Runtime-facing endpoint used by an executing task to report progress, intermediate output, completion, failure, or to pause for human input.
It is not the only way a run reaches a terminal state. The platform also polls the runtime and, on the transition to terminal, retrieves the result and stores output_data, duration_ms and outputs — so a task that merely returns a value is still tracked. A failure alert webhook from the runtime is a third path. Whichever writes a terminal status first wins; the others become no-ops.
What the callback adds is immediacy — polling can lag by up to one poll interval — and two states polling cannot observe: intermediate output during a run, and waiting for human-in-the-loop.
Endpoint
POST /api/runs/{runId}/callback
Auth: Authorization: Bearer <agent_api_key>
The runId comes from payload.runId in the task payload that the platform sends to your runtime at trigger time. The agent_api_key is delivered to the runtime as an environment variable on the runtime environment — it is not in the per-run payload.
Payload variants
The body is a discriminated union on type.
started
Marks the run as running.
{ "type": "started" }
output
Reports intermediate or terminal output.
{
"type": "output",
"output": { "step": 1, "pages_scanned": 12 }
}
To mark the run complete, add complete: true:
{
"type": "output",
"output": { "summary": "done" },
"outputs": 42,
"complete": true
}
To mark the run failed, add failed: true and an error:
{
"type": "output",
"failed": true,
"error": {
"name": "TimeoutError",
"message": "upstream did not respond within 30s"
}
}
| Field | Type | Description |
|---|---|---|
output | object | Domain payload (rendered in the platform UI). |
complete | boolean | Set true on success; transitions status → completed. |
failed | boolean | Set true on failure; transitions status → failed. |
error | object | { name?, message?, stack? } — required when failed=true. |
outputs | integer | Count of agent-defined output units; latest non-null value wins. |
waiting
Pause the run for human-in-the-loop input. Supported only by runtimes that implement waitpoints — check the agent's runtime descriptor.
{
"type": "waiting",
"tokenId": "wait_abc123",
"description": "Approve the generated summary",
"url": "https://your-app.example.com/review/abc123",
"output": { "draft": "…" }
}
| Field | Type | Description |
|---|---|---|
tokenId | string | Waitpoint token id used by the runtime to resume execution. |
description | string | Shown to the human reviewer. |
url | string | Optional. Absolute http(s) URL; when set, waiting-state notifications resolve {{run_url}} to this URL instead of the generic run-detail link — point reviewers at your own review UI. A malformed value is rejected with 400. |
output | object | Optional context displayed alongside the form. |
To resume a run paused this way, use Complete waitpoint (POST /api/runs/{runId}/waitpoint).
Response
200 OK with an empty body on success.
Errors
| Status | Reason |
|---|---|
401 | Missing or invalid agent_api_key. |
404 | runId does not exist or does not belong to this agent. |
409 | Backward state transition (e.g. callback after the run was cancelled). |
422 | Payload failed validation. |
429 | Per-agent rate limit. |
SDK
import { createAgentClient } from '@cxpa/sdk/agent'
const cxpa = createAgentClient({
baseUrl: process.env.CXPA_API_URL!,
// From the runtime environment — the payload never carries the agent key.
apiKey: process.env.CXPA_REVENUE_CHAT_AGENT_API_KEY!,
runId: triggerPayload.runId,
credentialsToken: triggerPayload.credentialsToken,
})
await cxpa.started()
await cxpa.output({ output: { step: 1 } })
await cxpa.complete({ output: { summary: 'done' }, outputs: 42 })