Executions API
These routes start and read runs of your deployed agents from your own server. The base URL is https://api.sapiom.ai. Send your API key in the x-api-key header.
Every route is scoped to the tenant of the API key. A run that belongs to another tenant answers 404, the same as a run that does not exist.
| Route | Purpose | Permission |
|---|---|---|
POST /v1/workflows/executions | Start a run | org.write |
GET /v1/workflows/executions | List runs | org.read |
GET /v1/workflows/executions/:id | Read one run | org.read |
GET /v1/workflows/executions/:id/spend | Read one run’s cost | org.read |
org.write includes org.read. A key created without an explicit permission list has full access and can call every route here. A key without the required permission gets 403.
Execution IDs are numeric strings, for example "4821". Any other :id answers 400 with code: "invalid_execution_id".
Start a run
Section titled “Start a run”POST /v1/workflows/executions
| Body field | Type | Required | Description |
|---|---|---|---|
definitionId | string | yes | The deployed agent to run. |
input | object | yes | Input for the entry step. Saved setting defaults are merged under it; your values win. The entry step’s inputSchema validates the result. |
idempotencyKey | string | no | Unique within your tenant, across all agents. A second start with the same key starts nothing and returns the run the key already started. |
Response 201:
{ "status": "enqueued", "executionId": "4821" }| Field | Description |
|---|---|
status | enqueued for a new run. already_exists when idempotencyKey was already used. |
executionId | The new run, or the run that already used the key. |
existingStatus | Only with already_exists: that run’s current status. |
The response confirms the run was queued. It is not the run’s result; read the run until its status is terminal.
Errors: 400 for a missing definitionId, an input that is not an object, input nested too deeply, or input the entry step’s schema rejects. 404 when the agent does not exist in your tenant.
List runs
Section titled “List runs”GET /v1/workflows/executions
Returns an array of runs, newest startedAt first.
| Query parameter | Description |
|---|---|
definitionId | Only runs of this agent. A numeric ID; a value that is not numeric matches no run. |
name | Only runs of agents with this name. Names can repeat across forks, so prefer definitionId. |
status | One or more of running, paused, completed, failed, cancelled: comma-separated (status=running,paused) or repeated. |
from | Runs started at or after this ISO 8601 time. |
to | Runs started at or before this ISO 8601 time. Applied only together with from. |
limit | Page size, 1 to 500. Default 100. |
offset | Rows to skip. |
paused is a run waiting for a signal; there is no separate waiting status. To page through a period without new runs shifting the offsets, fix both from and to and increase offset by limit until a page comes back shorter than limit.
An unknown parameter, an unknown status, an empty status, a malformed date, or a limit or offset out of range answers 400.
Each row has these fields. Rows do not include input, output, or error; read one run for those.
| Field | Description |
|---|---|
id | The execution ID. |
name, definitionId | The agent’s name and ID. |
buildRunId | The build the run executes. |
status | running, paused, completed, failed, or cancelled. |
currentStep, currentStepAttempt | The step in progress and its attempt number. |
idempotencyKey | The key the run was started with, or null. |
pausedSignalName, pausedSignalCorrelationId, pausedUntil | Set while the run is paused on a signal. |
startedAt, finishedAt | ISO 8601 times. finishedAt is null until the run is terminal. |
cost | { listUsd, billedUsd, ... } from a rollup that can lag, or null. Not final; use the spend route to bill. |
version | A counter for updates to the run row. Not a build or release version. |
Read one run
Section titled “Read one run”GET /v1/workflows/executions/:id
Returns every list field except cost, plus:
| Field | Description |
|---|---|
input | The input the entry step received, after defaults were merged. |
output | The final output. Set when status is completed. |
error | The terminal error. Set when status is failed or cancelled. |
steps | One entry per step attempt, in order, with its input, output, error, and timing. |
children | Runs this run dispatched: { executionId, targetType, correlationId, status }. |
parentExecutionId | The run that dispatched this one, or null for a run you started. |
rootExecutionId | The top run of the dispatch tree. Equals id for a run you started. |
triggerKind | How the run started: event, webhook, schedule, orchestration_dispatch, or null for an API start. |
Values that look like secrets are redacted, and large values are truncated.
Read a run’s cost
Section titled “Read a run’s cost”GET /v1/workflows/executions/:id/spend
{ "executionId": "4821", "status": "completed", "settled": true, "usage": [ { "meter": "llm.output_tokens.medium.standard", "quantity": 1840, "unit": "tokens", "listValueUsd": "0.0276" }, { "meter": "sandbox_compute_gbs", "quantity": 412.5, "unit": "gb_seconds", "listValueUsd": "0.004125" } ], "listValueUsd": "0.031725", "billedUsd": "0"}| Field | Type | Description |
|---|---|---|
executionId | string | The run. |
status | string | The run’s status, with the same values as GET /v1/workflows/executions/:id. |
settled | boolean | true once the figures below will not change. See Finality. |
usage | array | One line per meter the run consumed, highest list value first, unpriced lines last. |
usage[].meter | string | A stable meter key. |
usage[].quantity | number | Net units consumed, in unit. |
usage[].unit | string or null | The meter’s unit, for example tokens, gb_seconds, or requests. |
usage[].listValueUsd | string or null | The line’s value at your list price, as a decimal string. null when the meter has no USD price; an unpriced line is never reported as "0". |
listValueUsd | string or null | The sum of the priced lines. "0" for a run with no usage. null only when every line is unpriced. |
billedUsd | string | What your organization was charged for this run in metering charges: settled and pending charges for the run, plus its pro-rata share of any streamed-overage charge. "0" for usage inside your plan’s included quota. Per-call x402 payments are not included. |
Decimal strings carry up to 18 decimal places. Sum them with a decimal type, not floating point.
The response also contains fields used by the Sapiom dashboard (compute, capability, subtree, llm, totalUsd, organizationId, tenantId). They are not part of this contract and can change; read the fields in the table above.
The figures cover this run only. Runs it dispatched have their own spend; find them in children on GET /v1/workflows/executions/:id.
Finality
Section titled “Finality”Every meter except sandbox compute (sandbox_compute_gbs) is recorded by the time the run ends. Sandbox compute is reported by the sandbox provider after the sandbox shuts down. Measured over production runs, it arrived a median of 40 to 55 minutes after the run, within about 3 hours for 99% of runs, and never later than about 6 hours.
settled is true when all of these hold:
- the run is
completed,failed, orcancelled; - none of its sandboxes is still running;
- none of its charges is still pending;
- 8 hours have passed since the later of the run’s finish and its last sandbox shutdown.
The 8-hour wait applies to every run, including runs that appear to use no sandbox. If Sapiom cannot read the run’s metering, settled is false.
Related
Section titled “Related”- Attribute runs and cost to your customers: the platform guide that uses these routes, including reconciliation with your organization’s ledger and the transactions filters.
- Use signals: resume a paused run with
POST /v1/workflows/executions/:id/signals. - Runtime limits.
© 2026 Sapiom, Inc.