Workflow API
The workflow API runs published workflows from your own systems and reads their results. It is a deliberately small surface: two authenticated endpoints plus the public webhook ingest.
Base URL: https://api.app.getcontroller.ai
Authentication
Section titled “Authentication”Authenticate with an API key (sk- prefix) as a Bearer token:
Authorization: Bearer sk-...A cai_ CLI credential is a different credential type: it authenticates against the same host for CLI operations but is rejected by these endpoints (403). Server-to-server integrations use sk- keys.
API-initiated runs are billable like any other run, and a request can be refused up front with 400 "You have no remaining usage balance for this period."
Run a workflow
Section titled “Run a workflow”POST /api/v1/run-workflow{ "workflow_id": 123, "flow_id": "aB3xK9pQr2Zm", "flow_inputs": { "customer_email": "jane@acme.com" }, "mode": "live", "callback_url": "https://example.com/hooks/controller"}flow_idis a server-generated id, not a name. Get it (with each flow’s declared inputs and outputs) fromcai flow list, or viaPOST /workflow/get-workflow-flowswith the same API key.flow_inputsis validated strictly before anything runs: keys may be input names or display names; unknown keys are rejected (not ignored); values are type-checked; required inputs must be present; the same input under two keys is an error.modeislive(default — latest published release) ordev(the development version). ⚠️devis not a simulation: it is a real, billable run that performs real external actions; only the data/file rail is isolated. See dev runs.
Success response: { "status": "pending", "execution_id": "...", "error": null }.
Errors from this endpoint are 400s with the same envelope — including “Workflow not found” (unknown or not yours: API access is strictly per-account; org sharing does not extend keys). There is no 404 and no rate-limit 429. A 401 (bad/missing key) comes from the auth layer with a different body shape: {"statusCode":401,"error":{...},"data":null} — parse defensively.
Get results
Section titled “Get results”GET /api/v1/workflow-results/:execution_idResponse: { "status": "...", "execution_id": "...", "workflow_outputs": [{ "name": "...", "value": ... }], "error": "" }.
Statuses: pending, running, paused, completed, completed_with_error, failed, cancelled. Terminal states are completed, completed_with_error, failed, and cancelled — a polling loop must stop on all four, not just the first. paused (an approval gate or manual pause) can last indefinitely and cannot be resumed via this API — resolution happens in the app.
Error contract: 404 when the execution doesn’t exist, 403 when it belongs to another account, and 400 when it has aged past your plan’s run-history window — results are not retrievable forever (retention).
Where workflow_outputs comes from
Section titled “Where workflow_outputs comes from”Outputs are the merged results of every Return response node that completed in the run — last write wins per field. A flow with no Return response completes with workflow_outputs: []: a silent-success trap if you expected data. Design one Return per path (flow control).
Callbacks
Section titled “Callbacks”callback_url receives a POST when the run reaches a terminal state (all four — not only success). Delivery is at-most-once: a single unsigned POST, not retried, not persisted across restarts. Treat callbacks as a latency optimization and keep polling as the source of truth for anything that matters. (Shape quirk: a run with no outputs sends workflow_outputs: null in the callback but [] on the GET.)
Webhook ingest
Section titled “Webhook ingest”POST /api/events/:webhookIdThe URL shown on a webhook trigger. It is unauthenticated — the opaque id is the only secret, so treat the URL like a credential. It returns 200 even when no enabled trigger matches (events are silently dropped), so a 200 is delivery, not processing, confirmation. What happens next is the trigger lifecycle.
Example prompts
Section titled “Example prompts”Write a client for this workflow: fetch the flow id and inputs with cai flow list, call run-workflow, and poll with a loop that handles all four terminal states plus paused.