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: 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.
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.
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.
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.
{
"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_outcomescreate_outcomecreate_workflowpublish_workflowrun_workflowlist_executionsreplay_executioningest_outcome_event
{
"mcpServers": {
"outcomeos": {
"url": "https://outcomeos.cloud/mcp",
"headers": { "Authorization": "Bearer <your API key>" }
}
}
}Machine-readable
Docs your tools can read.
- /llms.txt
A short index of the docs for LLMs and answer engines.
- /llms-full.txt
The full docs in one plain-text file.
- /api/v1/openapi.json
OpenAPI spec for codegen and API clients.
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.