C3 AI Documentation Home

C3 AI Agentix Agent REST API

The Agent REST API lets your own code drive an Agentix agent over HTTP, outside the C3 AI Agentix chat UI. A programmatic caller — a CI job, a batch workflow, or another C3 application — can create an agent session, send prompts, read the streamed reply, exchange workspace files, and manage the session lifecycle. Everything is versioned under an /agent-api/v1/ surface.

This page is the endpoint reference. For a step-by-step walkthrough that authenticates and runs one session end to end, see C3 AI Agentix Developer Docs.

How the API is shaped

The surface splits across two planes. Every integration uses both.

PlaneWhere it runsWhat it carries
Control planeThe C3 AI Agentix app gateway, at <agentixBaseUrl>/agent-api/v1/Low-volume lifecycle and discovery: create, list, get, stop, resume, delete, bind app, definitions, models, status.
Data planeThe per-session agent pod, at <podUrl>/High-volume traffic: messages, the event stream, and files.

When you create a session, the control plane returns that session's pod_url. You then send messages, read events, and move files by calling the pod at that URL directly. The C3 AI Agentix app is not on the per-message path, so throughput scales with the number of sessions rather than a single shared app.

The two planes use slightly different path bases on the pod:

  • Messaging and the event stream sit under <podUrl>/agents/v1/sessions/<sessionId>/….
  • File operations sit under <podUrl>/agent-api/v1/sessions/…/<sessionId>.

A browser cannot call the pod directly: the pod does not accept an Authorization header from another web origin, and a browser event stream cannot set one at all. A browser integration must route every pod call through its own server-side backend, which holds the token and proxies to C3 AI Agentix. A server or command-line caller has no such restriction. See the embed guidance in C3 AI Agentix Developer Docs.

Authentication

How you authenticate depends on where your caller runs and whose identity C3 AI Agentix should see:

Your caller...Use...Identity C3 AI Agentix sees
Runs as a C3 application in the same environment as C3 AI AgentixThe platform's own session-token exchangeThe signed-in human, on their existing session
Runs anywhere, with no human behind itclient_credentialsA service identity (the OAuth client)
Runs in a different environment, acting as a specific signed-in humanauthorization_code with PKCEThe signed-in human, via a one-time OAuth exchange

A same-environment caller mints the platform's self-scoped User.mySessionToken on its own app and presents it to C3 AI Agentix as Authorization: c3auth <token>, with no OAuth application, no client secret, and nothing to register. The two OAuth flows need a one-time OAuth application registered on the C3 AI Agentix environment, scoped to Agentix.User, then exchange its credentials at <nexusBaseUrl>/oauth/token for an access token. Present either type of token on every control-plane and data-plane call:

Text
Authorization: Bearer <access_token>
Text
Authorization: c3auth <session_token>

The platform's authenticator accepts both schemes interchangeably. The full registration and token-minting steps for all three paths are in C3 AI Agentix Developer Docs.

Authentication errors

  • 401 — the credential is missing, invalid, or expired. Mint a fresh session token (same environment), re-mint (headless), or refresh (external signed-in user).
  • 403 — the caller lacks the Agentix.User role, or does not own the session.

Match on the HTTP status, not the response body: the platform does not return a machine-readable error code and does not distinguish an expired credential from an invalid one.

Session lifecycle

A session is one chat thread and the only resource you address. The compute behind it is managed for you and never appears in a response.

GET /agent-api/v1/sessions/get/{sessionId} reports one of these states:

StateMeaning
STARTINGThe session is warming. A cold start can take a few minutes.
READYWarm and reserved, before the first message.
ACTIVEAt least one message has been exchanged.
PAUSEDStopped, with files and transcript preserved. Wakes on the next call.
ERRORUnrecoverable failure; a failure_reason is returned.

A terminated or removed session returns 410 Gone and is excluded from list results by default.

Two behaviors follow from this model:

  • Cold-start back-off. A STARTING response carries a Retry-After header. Honor it rather than polling in a tight loop.
  • Wake-on-message. A call against a PAUSED session resumes it transparently — you do not orchestrate a separate resume first. Because the transcript lives with the session, reading history from a PAUSED session also wakes it, so the first read after a pause can incur a cold start.

Endpoint reference

Full paths are shown. Read the /agent-api/v1 (control plane) or the pod base (<podUrl>/agents/v1 or <podUrl>/agent-api/v1) as part of the path. The machine-readable contract is served live at GET /agent-api/v1/openapi.json and can be opened with any OpenAPI tool (Swagger UI, Redoc).

Session lifecycle (control plane)

MethodPathPurpose
POST/agent-api/v1/sessions/createReserve or create a session, with an optional first message. Returns 202 + id + state.
GET/agent-api/v1/sessions/listList the caller's sessions (paginated; excludes terminated by default).
GET/agent-api/v1/sessions/get/{sessionId}Status and metadata. 410 Gone if terminated or removed.
POST/agent-api/v1/sessions/stop/{sessionId}Stop, preserving workspace state (→ PAUSED). Acts on the whole pod.
POST/agent-api/v1/sessions/resume/{sessionId}Resume a paused session (also implicit on the next call). Acts on the whole pod.
DELETE/agent-api/v1/sessions/delete/{sessionId}Soft-remove one chat thread (→ 410 thereafter).

Stop and resume act on the whole pod, so every session on it pauses or wakes together.

Messaging and events (data plane)

Served by the pod under <podUrl>/agents/v1/sessions/{sessionId}/….

MethodPathPurpose
POST<podUrl>/agents/v1/sessions/{sessionId}/messagesSend a message; returns a turn_id. Wakes a PAUSED session.
GET<podUrl>/agents/v1/sessions/{sessionId}/eventsPoll (?start_id=, ?limit=) or stream (?stream=true, SSE).
POST<podUrl>/agents/v1/sessions/{sessionId}/interruptAbort the current turn, record an InterruptEvent, stay ready.
GET<podUrl>/agents/v1/sessions/{sessionId}/statusExecution status (idle / running / error).

Messages are read as a filter over events (kind=MessageEvent), not a separate resource.

Files (data plane)

Served by the pod under <podUrl>/agent-api/v1/sessions/…. Workspace-relative file paths ride a ?path= query parameter.

MethodPathPurpose
POST/agent-api/v1/sessions/files/{sessionId}Multipart upload to the workspace (field file).
GET/agent-api/v1/sessions/files/{sessionId}List (optionally a ?path= subtree).
GET/agent-api/v1/sessions/files-tree/{sessionId}Hierarchical tree (?scope=session|workspace).
GET/agent-api/v1/sessions/file-preview/{sessionId}?path=Preview (text, image, or binary stub).
GET/agent-api/v1/sessions/file-view/{sessionId}?path=Full text view.
POST/agent-api/v1/sessions/file-move/{sessionId}Move or rename ({ from, to, overwrite? }).
GET/agent-api/v1/sessions/file/{sessionId}?path=Download a single file.
GET/agent-api/v1/sessions/files-zip/{sessionId}Download the whole workspace as a zip.

Paths are workspace-relative. Absolute paths, .. traversal, and protected paths are rejected before any filesystem access. A move that would overwrite without overwrite: true returns 409 { conflict: true }; an upload with no file field returns 400.

Not every backend supports every file operation. A files-tree response may carry a capabilities object advertising which operations the backend supports; an unsupported operation returns 501 with the same flags.

Exec (data plane)

MethodPathPurpose
POST<podUrl>/agent-api/v1/sessions/{sessionId}/execRun a shell command in the workspace ({ command, timeout_seconds? }{ stdout, stderr, exit_code }).

A non-zero exit code returns 200 with the result body; the caller decides whether to treat it as a failure. exec is available only on the hosted OpenHands Cloud backend, with a default and maximum timeout of 900 seconds. The in-pod agent returns 501 and advertises its supported operations in a capabilities object — use the file operations or the agent's own tools instead.

Apps (target binding)

A session is created independent of any C3 app, and may run bound to none, operating only against external services for which the caller has supplied credentials. Bind target apps afterward on the live session; all bound apps are equal (there is no primary).

MethodPathPurpose
POST/agent-api/v1/sessions/bind-app/{sessionId}Bind a target app (app_id in the body).
DELETE/agent-api/v1/sessions/unbind-app/{sessionId}?appId=Unbind a target app.

Binding grants the agent a restricted token for the target app, minted internally; token issuance is never an API operation. Unbinding an app that is not bound is a no-op.

Discovery and health

MethodPathPurpose
GET/agent-api/v1/definitionsList the agent definitions you can pass as agent_name.
GET/agent-api/v1/modelsList the LLM models you can request for a session.
GET/agent-api/v1/sessions/skills/{sessionId}List the skills a session loaded.
GET/agent-api/v1/users/meYour identity and your active-session count.
GET/agent-api/v1/statusHealth of your current service, including whether the pod is reachable.
GET/agent-api/v1/openapi.jsonThe full OpenAPI 3.1 wire contract, served verbatim.

GET /agent-api/v1/status returns { service_id, status, ready, reachable, failure_reason, pod_url }. status is always readable and reports states a live probe cannot reach (STARTING, PAUSED, failed); reachable is a best-effort liveness probe of the pod; ready means a serving state that is also currently reachable. A caller with no service gets 200 with status: "NONE", not 404.

Creating a session

POST /agent-api/v1/sessions/create accepts a JSON body:

FieldRequiredMeaning
agent_namenoWhich agent definition to use (default default). Discover with GET /agent-api/v1/definitions.
modelnoLLM model override for the session.
initial_messagenoFirst prompt, sent as soon as the session is ready.
system_instructionsnoCustom additions to the system prompt.
metadatanoCaller-defined key-value pairs for your own tracking.
idempotency_keynoA retry with the same key returns the original session instead of creating a second one.

The call returns 202 Accepted with the session id, an initial STARTING state, and the pod_url for the data-plane calls that follow. On a cold start the call still returns immediately; an initial_message is queued and delivered automatically when the session reaches READY — it is neither dropped nor must you resend it. Poll GET /agent-api/v1/sessions/get/{sessionId} (honoring Retry-After) or listen on the event stream for the state to reach READY or ACTIVE.

Response behaviors

Synchronous and asynchronous messages

Sending is asynchronous by default: POST …/messages returns 202 { turn_id } and you read the result from the event stream. For a short turn, request a blocking send with ?wait=true or the header Prefer: wait=<seconds> (default 30 seconds, maximum 300). The connection is held until the turn finishes and returns 200 { turn_id, status, events } inline (a failed turn surfaces as status: error). If the turn runs past the cap, the call falls back to the async 202 { turn_id } and you continue on the event stream.

One turn per session

Only one turn runs per session at a time. A second POST …/messages while a turn is running returns 409 "a turn is already running".

Reconnecting

A dropped stream or poll connection does not cancel the turn — it keeps running, and you reattach through the event stream. Poll from your last start_id, or resume a stream with the Last-Event-Id header (or last_event_id query parameter).

Idempotency

POST …/messages and POST …/files honor an Idempotency-Key HTTP header; POST /sessions/create takes an idempotency_key body field (the control-plane transport differs). A retry with the same key replays the original response without repeating the side effect — no duplicate turn, upload, or session. Dedup is on the key alone: the client owns minting a fresh key per distinct request. Two concurrent requests with the same key cannot both run — the racing one gets 409 idempotency_in_progress, carrying the in-flight turn_id for a messages send so it can attach to the original turn's stream.

Rate limits

Token-authenticated (API) requests are rate-limited per session. On exhaustion the pod returns 429 with a Retry-After header; every response carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset so you can pace requests instead of retrying blind. A separate per-pod cap on concurrent in-flight turns also returns 429 + Retry-After when the pod is saturated. Back off for the stated interval.

Events and the transcript

A single monotonic event stream is the canonical record of a session. Each event carries a monotonic id, a timestamp, a kind (MessageEvent, ActionEvent, StatusEvent, InterruptEvent), and a source. The agent's text lives in MessageEvents, in the content array.

Reading the stream:

  • Poll GET …/events?start_id=<n>&limit=<m>. The response is { events, next_start_id, status, stream_epoch }. Set the next poll's start_id to next_start_id. Paging is strictly forward (id > start_id).
  • Stream GET …/events?stream=true returns a text/event-stream. Resume a dropped stream with Last-Event-Id.
  • stream_epoch identifies the current run of the session. If its value changes between reads, the session restarted and renumbered its events; re-sync from start_id=0.

A status of idle means the current turn has finished.

Retention

  • A PAUSED session keeps its workspace files and transcript. By default they are retained indefinitely; a deployment may configure a bounded retention window, so confirm the policy for your environment.
  • A terminated session loses its workspace and transcript. Export anything you need to keep with files-zip before deleting or terminating a session.

Versioning

Every route lives under /agent-api/v1/. There are no unversioned routes. A breaking change to a v1 shape ships behind a Sunset header alongside a parallel /agent-api/v2/, never as an in-place change to v1.

See also

Was this page helpful?