Public beta — not for production use. Data may be wiped at any time. Questions? Contact us.
Documentation menu

HTTP API reference

Every invocation endpoint — flow invoke, SSE streaming, single-node calls — with authentication, request and response fields, rate limits and daily quotas, and the structured error envelope.

View as Markdown

This page is the reference for Axiom's invocation HTTP surface: invoking a flow (POST /v1/flows/invoke), streaming a pipeline-mode flow (POST /v1/flows/invoke/stream), invoking a single node, the error envelope, and rate limits. For a guided walkthrough, start with Invoke a flow via API. For the exact input and output schema of your flow or package, use the live OpenAPI specs — they are generated from the compiled artifact and are always current.

Base URL and authentication

All invocation endpoints are served on your deployment's origin under the /invocations path prefix. For example:

POST https://<your-axiom-origin>/invocations/v1/flows/invoke

Every request must carry an API key as a bearer token:

Authorization: Bearer <your 64-character API key>

API keys are 64-character hex strings created under Console → API Keys in the app, or minted automatically by axiom login (named cli). The raw key is shown exactly once at creation; the platform stores only its SHA-256 hash. See Create and manage API keys.

The key resolves to your tenant. Every execution it starts is scoped to that tenant — the platform enforces isolation, so a key can never invoke another tenant's flows or read another tenant's data (Sandboxing and tenancy).

A missing, invalid, or revoked key gets HTTP 401 with the body:

{"error": "unauthorized"}

Revocation takes effect on the key's very next use — there is no key cache.

Rate limits

Two per-tenant token buckets apply to authenticated requests:

  • All routes: 100 requests/second, burst 200.
  • Invocation routes (everything under /invocations/): an additional 5 requests/second, burst 10 — a beta guardrail.

Exceeding either returns HTTP 429 with a Retry-After: 1 header and the body {"error":"rate limit exceeded"}.

Daily quotas

On top of the per-second rate limits, each tenant has two daily budgets. Both reset at midnight UTC. The beta defaults are:

  • 2,000 invocations per day. An invocation is one authenticated request that triggers compute: a flow invoke (plain or streaming), a single-node call, a paused-flow resume (including resume webhooks), or a debug-session fork.
  • 500 executions per day. An execution is one flow run. Most invocations start exactly one, but executions a flow spawns internally — parallel fan-out branches that run as child executions, runtime graph mutations, debug forks — each draw from the same budget. A fan-out into 10 branches costs 10 executions.

A request that would exceed either budget is rejected before any compute runs, with HTTP 429, a Retry-After header, and a structured body whose retry_after_seconds counts down to the next UTC midnight:

{
  "error": "quota_exceeded",
  "message": "daily invocations quota exceeded (cap 2000)",
  "retry_after_seconds": 74380
}

If a running flow exhausts the execution budget mid-run (for example at a large fan-out), the flow fails with the same quota exceeded message as its error.

The defaults are per-tenant and the operator can raise or lower them for your tenant — if you hit them with a legitimate workload, get in touch. Two related responses you may also see on invocation routes:

StatusBody errorMeaning
403tenant_suspendedYour tenant has been disabled by the operator. No invocations or resumes are accepted. Contact the operator.
503quota_unavailableThe quota service could not be reached, so the request was rejected as a safety measure. Transient — retry after retry_after_seconds (5 s).

Invoke a flow

POST /v1/flows/invoke executes a compiled artifact once and returns the result inline (with wait) or an execution ID immediately (without).

curl -X POST 'https://flows.example-axiom-host.com/invocations/v1/flows/invoke' \
  -H "Authorization: Bearer $AXIOM_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "graph_id": "01JX3F8Q4ZJ4M9W4Y0B8T2K7RD",
  "input": { "text": "hello" },
  "wait": true
}'

Request fields

FieldTypeRequiredMeaning
graph_idstringyesThe compiled artifact to execute. Get it from the Use via API dialog or the flow's live OpenAPI spec; editing a flow produces a new artifact with a new ID.
inputobjectone of input/payloadThe entry node's input message as a JSON object. The platform converts it to the typed message, and decodes the result back to JSON.
payloadstring (base64)one of input/payloadThe entry node's input message as protobuf-encoded bytes, base64-encoded. For callers that already speak protobuf. input takes precedence if both are set.
waitbooleannotrue blocks until the execution completes and populates result. Default false: the response returns immediately with just accepted and execution_id.
timeout_secondsintegernoHow long a waiting call blocks before giving up (default 30).
config_idstringnoNamed flow config profile to apply; when omitted the default hierarchy applies (tenant default → flow default). See Flow configs.
debug_session_idstringnoStreams per-node debug events for this execution to the named debug session. Can also be sent as the X-Debug-Session-Id header, which takes precedence over the body field. See Debug a flow.

Response

An accepted invocation returns HTTP 202:

{
  "accepted": true,
  "execution_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "result": {
    "success": true,
    "output": { "text": "hello" },
    "completed_at": 1765432100000
  }
}
  • execution_id — a 32-character hex ID assigned when the request is accepted. It is also the execution's trace ID, so one ID references the execution everywhere: results, debug events, and traces.
  • result — present only with wait: true. result.output is the terminal node's output message decoded to JSON (when you invoked with input); result.payload carries base64-encoded protobuf bytes instead when you invoked with payload or when JSON decoding was not possible.
  • Without wait, the body is just {"accepted": true, "execution_id": "..."} and the flow runs asynchronously.

Failures return a non-202 status with an error body — see Error envelope.

Stream a flow over SSE

POST /v1/flows/invoke/stream invokes a flow in pipeline mode and streams result frames back as Server-Sent Events. Use it for flows that emit a sequence of output frames rather than a single result (Execution model).

curl -N -X POST 'https://flows.example-axiom-host.com/invocations/v1/flows/invoke/stream' \
  -H "Authorization: Bearer $AXIOM_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "graph_id": "01JX3F8Q4ZJ4M9W4Y0B8T2K7RD",
  "input": { "text": "hello" }
}'

The request body accepts graph_id, input (or payload), timeout_seconds, config_id, and debug_session_id exactly as in Invoke a flow. wait does not apply — frames are always delivered as they are produced.

The response is Content-Type: text/event-stream. Each event is one data: line of JSON:

data: {"execution_id":"4bf92f3577b34da6a3ce929d0e0e4736","frame_index":0,"payload":{"text":"chunk 1"},"is_final":false}

data: {"execution_id":"4bf92f3577b34da6a3ce929d0e0e4736","frame_index":1,"payload":{"text":"chunk 2"},"is_final":true,"success":true}

Frame fields:

FieldMeaning
execution_idSame ID as the unary endpoint — also the trace ID.
frame_indexPosition of this frame in the stream, starting at 0.
payloadThe terminal node's output message for this frame, decoded to JSON. Omitted when the frame carries no decodable payload.
is_finaltrue on the last frame; the stream ends after it.
successtrue on the final frame of a successful execution. On failure the field is omitted entirely (it is never serialized as false) — treat a final frame without success: true as failed and read error.
errorError message when a frame reports failure.

The stream times out after 30 seconds by default; set timeout_seconds to extend it. A timeout ends the stream with a final frame whose error is "timeout waiting for pipeline result".

Invoke a single node

A published node can be called directly, without composing a flow. This is the endpoint that Client Builder SDKs use — one method per node.

curl -X POST 'https://flows.example-axiom-host.com/invocations/v1/nodes/axiom-official/pdf-chunker/1.0.0/Chunk' \
  -H "Authorization: Bearer $AXIOM_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{ "url": "https://example.com/doc.pdf" }'
  • Path forms: POST /v1/nodes/{owner}/{package}/{version}/{node} (name-based, as in the example) or POST /v1/nodes/{node_ulid} (the node's 26-character registry ID).
  • Request body: the node's input message as a plain JSON object — no envelope.
  • Response: HTTP 200 with the node's output message as JSON. A node that returns an error gets HTTP 422 with {"error_message": "..."}; an unknown or undeployed node gets HTTP 404.
  • Streaming nodes: a pipeline-mode node (or any request with Accept: text/event-stream) streams its output frames as Server-Sent Events instead of a single JSON response.

A binary-protobuf variant exists at POST /v1/nodes/invoke with the JSON body {"node_id": "...", "payload": "<base64 protobuf bytes>"}, returning {"success": ..., "payload": ..., "error_message": ...} — for callers that serialize the messages themselves.

Error envelope

Invocation failures with a known cause return a structured body with a stable, machine-readable error class:

{
  "error": "payload_too_large",
  "message": "payload exceeds hard cap: actual=18874368 max=16777216",
  "max_bytes": 16777216,
  "actual_bytes": 18874368
}
Statuserror classExtra fieldsWhen
400{"accepted": false, "error_message": "invalid request body: ..."}The request body is not valid JSON.
401unauthorizedMissing, invalid, or revoked API key.
413payload_too_largemax_bytes, actual_bytes (when the rejecting code path knows the sizes)The input payload exceeds the platform's 16 MiB hard cap.
422{"error_message": "..."}Single-node invocation only: the node itself returned an error.
429rate limit exceededRetry-After: 1 headerPer-tenant rate limit exceeded.
429quota_exceededretry_after_seconds to next UTC midnight, mirrored in the Retry-After headerPer-tenant daily quota (invocations or executions) exhausted.
403tenant_suspendedYour tenant has been disabled by the operator.
503quota_unavailableretry_after_seconds: 5The quota service is unreachable; the request is rejected as a safety measure. Retry with backoff.
503upstream_unavailableretry_after_seconds: 5The flow could not be queued; a platform dependency was temporarily unavailable. Retry with backoff.
503blob_storage_unavailableretry_after_seconds: 5A large input payload could not be stored; a platform dependency was temporarily unavailable. Retry with backoff.
500{"accepted": false, "error_message": "..."}Any failure that does not classify into the rows above (for example, a missing graph_id).

Errors inside a successful HTTP exchange surface differently: a waited invoke returns 202 with result.success: false, and a stream delivers a final frame with error set and no success: true (the frame's success field is omitted on failure, never serialized as false). The error catalog lists the error messages themselves.

Live OpenAPI specs

Hand-maintained docs cannot know your flow's input schema — the live, generated OpenAPI 3.0 specs can. Use them as the authoritative request and response schemas:

  • Per flow: GET /api/graphs/{artifact_id}/openapi.json (authenticated, same bearer key). Generated on demand from the compiled artifact: the entry node's input message is the documented input schema, the terminal node's output message is the result schema, and graph_id is pinned to that exact artifact. Pipeline-mode flows document /v1/flows/invoke/stream; unary flows document /v1/flows/invoke. The same spec backs the Open interactive docs button in the flow inspector's API section — see Use the interactive API docs.
  • Per package: GET /api/packages/{name}@{version}/openapi.json (public, no auth). One POST /v1/nodes/{owner}/{package}/{version}/{node} operation per node, with input and output schemas and examples. An interactive try-it page for the same spec is served at GET /api/packages/{name}@{version}/docs.