Skip to main content

Agent API (/v1)

Used by: n8n, Zapier, MCP, and any HTTP client. Get X-Agent-Id and X-Api-Key from Credentials. Per-request timeout overrides in the JSON body are REST-only. The MCP integration does not forward timeout; expiry always uses the agent’s dashboard defaults unless you call this API over HTTP. All routes require agent authentication. A machine-readable description of these endpoints is available as OpenAPI YAML. For plan and quota errors on create, see Automation limits & billing.

Approval status values

The status field describes the approval lifecycle (human choice or expiry). Values you will see on GET /v1/approvals/:requestId, GET /v1/approvals/:requestId/result, and in callback bodies:
ValueMeaning
pendingOpen; waiting for a human or for expiry. Poll responses may include only request_id and status.
selected_optionResolved: the human chose one of the configured options.
custom_instructionResolved: the human provided free-text instruction instead of an option.
expiredClosed by timeout (no human resolution in time).
The create response’s status is pending for a newly created row.

Result delivery fields

On GET /v1/approvals/:requestId only, the API also returns how the final outcome was (or was not) handed off when a callback URL was configured on create. This is separate from approval status above.
FieldValuesMeaning
result_delivery_statuspendingNo callback, or callback not completed successfully yet, or consumer is expected to poll for the result.
deliveredCallback URL returned a successful HTTP response (2xx) after resolution.
failedCallback retries were exhausted; see result_delivery_error.
result_delivery_errorstring or nullShort error message when delivery failed (for example HTTP status or network error).
If you did not set callback.url on create, the service still tracks completion via polling; result_delivery_status typically stays pending from a “callback delivery” perspective even after the approval is resolved.

Callback webhooks

If you include callback on POST /v1/approvals, the service sends the outcome to your URL when the request leaves pending (human resolution or timeout expiry).

Request the service makes to your URL

AspectBehavior
HTTP methodPOST
URLThe callback.url you supplied at create time
HeadersContent-Type: application/json, then any custom keys from callback.headers (if set), then optional agent-level callback authentication from the dashboard (same header name + value you configure for tools such as n8n Wait Header Auth). If the same header name appears in both places, the agent value wins so clients cannot strip auth by omitting it from the create payload.
BodySame JSON object as GET /v1/approvals/:requestId/result returns for a resolved request (not the minimal pending shape). Same fields as described under Resolved (200) below.
SuccessAny 2xx response is treated as success; no response body is required.
RetriesOn non-2xx or network failure, the service retries with exponential backoff (defaults in the open-source server: base delay 1s, max delay 30s, up to 3 attempts—your operator can tune env vars).
SecurityPrefer HTTPS and optional agent-level callback auth in the dashboard (merged at delivery; the secret is not part of POST /v1/approvals). Use callback.headers only for non-secret extras (for example tracing IDs). There is no HMAC body signature in the reference server; see n8n integration for Wait-compatible header auth.
Expiry-driven resolutions trigger the same callback path once the timeout worker marks the request expired.

Rate limits

POST /v1/approvals is additionally limited by a per-agent sliding window in the API process to absorb bursts. Default in the reference server configuration: 60 successful create attempts per agent per 60 seconds (your deployment may override AGENT_CREATE_RATE_LIMIT_MAX and AGENT_CREATE_RATE_LIMIT_WINDOW_MS). When exceeded, the API returns 429 with { "error": "rate_limited" }. Backoff and retry with jitter; do not spin in a tight loop.

POST /v1/approvals

Creates an approval request. The body must match the API validation rules below.

Request body

FieldTypeRequiredNotes
request_idstringno1–120 chars when set; omit or leave blank and the server assigns a UUID (see response request_id). Supply your own stable id for idempotent retries.
correlation_idstringnomax 120
questionstringyesmax 500
context_markdownstringyesmax 2500
notification_summarystringnomax 120
optionsarrayyes1–4 items
options[].labelstringyesmax 120
options[].valuestringyesmax 500
timeoutobjectnoOmit to use this agent’s default timeout from the dashboard. If present, each field you send overrides only that part of the defaults (partial override).
timeout.expires_in_secondsnumberno*integer, 30–86400; *required in the merged result (from body or agent default)
timeout.modestringno*expire_no_action or expire_with_predefined_instruction; *same as above
timeout.predefined_instructionstring | nullno*When non-null, max 1000 chars. After merge, required to be non-empty when mode is expire_with_predefined_instruction
callbackobjectno
callback.urlstring (URL)yes if callback setValid URL
callback.headersrecord of stringnoOptional extra headers (non-secret metadata). Do not rely on this for auth secrets when agent callback auth is enabled in the dashboard—the server merges the configured auth header at delivery.
Omitting timeout: The server merges a missing timeout with the agent’s saved defaults (default_timeout_seconds, default_timeout_mode, default_timeout_instruction). If you include timeout, only the properties you set replace the corresponding defaults; omitted properties keep the agent values. The merged timeout is validated (including instruction required for expire_with_predefined_instruction) and used for expires_at and idempotency hashing. Omitting request_id: The create response always includes request_id (either yours or the server-generated UUID). Use that value for polling. Repeating POST /v1/approvals without a client request_id assigns a new id each time, so blind retries can create duplicate approvals. For safe retries, send the same request_id you chose on the first attempt.

Success response (200)

{
  "approval_id": "<uuid>",
  "request_id": "<external id: yours or server-generated>",
  "status": "<string>",
  "expires_at": "<iso8601>",
  "idempotent": true
}
idempotent is true when this call matched an existing equivalent request for the same idempotency identity (only when you supplied a request_id; server-assigned ids are never merged across calls).

Error responses

HTTPerrorWhen
400invalid_payloadValidation failed; issues may be present
401missing_agent_auth, invalid_agent, invalid_api_keyAuth failure
403USAGE_APPROVAL_LIMITTeam is over the subscription/plan approval quota (when billing/usage limits are enabled). Body includes code: "USAGE_APPROVAL_LIMIT" and over_limit: true.
409idempotency_conflictSame request_id for this agent but incompatible canonical payload (see body below)
429rate_limitedToo many creates per agent in the sliding window
500approval_creation_failedUnexpected failure
409 idempotency_conflict body (in addition to error):
{
  "error": "idempotency_conflict",
  "message": "<human-readable explanation; idempotency compares question, context, options, merged timeout, callback URL and headers>",
  "request_id": "<the external id you sent>",
  "existing_approval_id": "<uuid of the approval row already stored>"
}
Use a new request_id when you intend a separate approval; repeat the same body to retry safely.

GET /v1/approvals/:requestId

Returns metadata for an approval identified by your external request_id (the path parameter is that same string). Use this for dashboards or to inspect callback delivery state; use /result for the full outcome payload.

Success response (200)

{
  "request_id": "<string>",
  "status": "<string>",
  "expires_at": "<iso8601>",
  "result_delivery_status": "<string>",
  "result_delivery_error": "<string|null>"
}
status follows Approval status values. result_delivery_status / result_delivery_error are described under Result delivery fields.

Error responses

HTTPerrorWhen
401(agent auth)Invalid credentials
404not_foundNo approval for this agent + external id

GET /v1/approvals/:requestId/result

Polls the outcome. The path parameter :requestId is your external id (request_id from creation).

Pending (200)

{
  "request_id": "<string>",
  "status": "pending"
}

Resolved (200)

Includes fields such as:
  • request_id, agent_id, correlation_id
  • status — one of selected_option, custom_instruction, or expired (see Approval status values)
  • selected_option_index, selected_option_label (when applicable)
  • custom_instruction
  • resolved_by (user_id, name), resolved_at
This is the same JSON shape POSTed to your callback.url, if configured.

Error responses

HTTPerrorWhen
401(agent auth)Invalid credentials
404not_foundUnknown approval for this agent + external id