Skip to content

Developers

Reliable workflow execution, over an API.

Queues, retries, idempotency, waits and replay — so you don't have to build them. Call it from your backend, your product or your AI agent.

REST API and authentication

Everything in the app is available over JSON at /api/v1. Authenticate with an API key as a Bearer token. Keys are created in Settings, shown once, and stored hashed.

The OpenAPI document lives at /api/v1/openapi.json. Errors share one shape with a stable code and a request ID.

Authorization header
Authorization: Bearer <your API key>

Workflow execution API

Start a published workflow with POST /api/v1/workflows/{id}/run. Pass an idempotency_key so a retry from your side never creates a second run. The call returns the execution ID right away; the work runs from a queue.

Run a workflow
curl -X POST https://outcomeos.cloud/api/v1/workflows/wf_lead_response/run \
  -H "Authorization: Bearer $OUTCOMEOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "lead_381",
    "input": { "name": "Aisha K.", "phone": "+15551234567" }
  }'

# Same idempotency_key again → the existing execution is returned, nothing runs twice.

Signed webhooks

Webhook triggers require a signature. Send X-OutcomeOS-Signature: t=<unix>,v1=<hex>, where v1 is the hex HMAC-SHA256 of "<t>.<rawBody>" with the workflow's signing secret.

Requests with a timestamp more than 5 minutes off are rejected, and a bad signature never starts a run. The verifier below is the same check, so you can test your signing code.

verify.js
import crypto from "node:crypto";

// header: X-OutcomeOS-Signature: t=<unix>,v1=<hex>
export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // 5-minute tolerance
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1 ?? "", "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Connector SDK

New integrations are plain TypeScript objects: an id, a testConnection that must pass before a connection counts, and actions that each declare whether they're idempotent. The engine uses that flag to decide what's safe to retry. No changes to the engine needed.

packages/connector-sdk
import { connectorFetch, requireInput, type Connector } from "@outcomeos/connector-sdk";

export const acmeCrm: Connector = {
  id: "acme_crm",
  name: "Acme CRM",
  implemented: true,
  requiredPlatformEnv: [],
  secretFields: ["token"],
  async testConnection(ctx) {
    const { body } = await connectorFetch(ctx, "https://api.acme.example/me", {
      headers: { Authorization: `Bearer ${ctx.credentials.token}` },
    });
    return { ok: true, message: "Connected", details: { account: String((body as { name?: string }).name) } };
  },
  actions: {
    send_message: {
      key: "send_message",
      name: "Send message",
      description: "Message a contact",
      idempotent: false, // never blindly retried; unknown results go to a human
      inputs: [{ key: "contact_id", label: "Contact", required: true }],
      async execute(ctx) {
        const { body } = await connectorFetch(ctx, "https://api.acme.example/messages", {
          method: "POST",
          headers: { Authorization: `Bearer ${ctx.credentials.token}`, "Idempotency-Key": ctx.idempotencyKey },
          body: JSON.stringify({ contact: requireInput(ctx.inputs, "contact_id") }),
        });
        return { output: body as Record<string, unknown> };
      },
    },
  },
};

Execution logs

Every execution has an ID, and every step records input, output, status, timestamps, retry count, error class and idempotency key. Secrets and values that look like credentials are masked before they're stored. Replay a failed run via the API or the app.

Example execution log (masked, sample data)
{
  "id": "ex_8f27",
  "status": "completed",
  "steps": [
    { "step_key": "enrich", "status": "completed", "retry_count": 2,
      "error": null, "idempotency_key": "ex_8f27:enrich" },
    { "step_key": "send_whatsapp", "status": "failed", "retry_count": 0,
      "error": { "class": "auth", "message": "401 token expired" } },
    { "step_key": "send_sms", "status": "completed",
      "input": { "to": "+1555•••4567", "auth": "Bearer ••••3f9a" } }
  ]
}

MCP server

The MCP server is live at /mcp. It's stateless and uses the same API-key Bearer auth, so an agent only sees what its key can see.

  • list_outcomes
  • create_outcome
  • create_workflow
  • publish_workflow
  • run_workflow
  • list_executions
  • replay_execution
  • ingest_outcome_event

MCP setup guide →

MCP client config
{
  "mcpServers": {
    "outcomeos": {
      "url": "https://outcomeos.cloud/mcp",
      "headers": { "Authorization": "Bearer <your API key>" }
    }
  }
}

Machine-readable

Docs your tools can read.

Embedding automation in your own product? See SaaS embedded automation.

Get an API key in two minutes.

Sign up free, create a key in Settings, and run your first workflow from curl.