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.
| Plane | Where it runs | What it carries |
|---|---|---|
| Control plane | The 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 plane | The 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 Agentix | The platform's own session-token exchange | The signed-in human, on their existing session |
| Runs anywhere, with no human behind it | client_credentials | A service identity (the OAuth client) |
| Runs in a different environment, acting as a specific signed-in human | authorization_code with PKCE | The 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:
Authorization: Bearer <access_token>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 theAgentix.Userrole, 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:
| State | Meaning |
|---|---|
STARTING | The session is warming. A cold start can take a few minutes. |
READY | Warm and reserved, before the first message. |
ACTIVE | At least one message has been exchanged. |
PAUSED | Stopped, with files and transcript preserved. Wakes on the next call. |
ERROR | Unrecoverable 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
STARTINGresponse carries aRetry-Afterheader. Honor it rather than polling in a tight loop. - Wake-on-message. A call against a
PAUSEDsession resumes it transparently — you do not orchestrate a separate resume first. Because the transcript lives with the session, reading history from aPAUSEDsession 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)
| Method | Path | Purpose |
|---|---|---|
| POST | /agent-api/v1/sessions/create | Reserve or create a session, with an optional first message. Returns 202 + id + state. |
| GET | /agent-api/v1/sessions/list | List 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}/….
| Method | Path | Purpose |
|---|---|---|
| POST | <podUrl>/agents/v1/sessions/{sessionId}/messages | Send a message; returns a turn_id. Wakes a PAUSED session. |
| GET | <podUrl>/agents/v1/sessions/{sessionId}/events | Poll (?start_id=, ?limit=) or stream (?stream=true, SSE). |
| POST | <podUrl>/agents/v1/sessions/{sessionId}/interrupt | Abort the current turn, record an InterruptEvent, stay ready. |
| GET | <podUrl>/agents/v1/sessions/{sessionId}/status | Execution 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.
| Method | Path | Purpose |
|---|---|---|
| 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)
| Method | Path | Purpose |
|---|---|---|
| POST | <podUrl>/agent-api/v1/sessions/{sessionId}/exec | Run 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).
| Method | Path | Purpose |
|---|---|---|
| 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
| Method | Path | Purpose |
|---|---|---|
| GET | /agent-api/v1/definitions | List the agent definitions you can pass as agent_name. |
| GET | /agent-api/v1/models | List 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/me | Your identity and your active-session count. |
| GET | /agent-api/v1/status | Health of your current service, including whether the pod is reachable. |
| GET | /agent-api/v1/openapi.json | The 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:
| Field | Required | Meaning |
|---|---|---|
agent_name | no | Which agent definition to use (default default). Discover with GET /agent-api/v1/definitions. |
model | no | LLM model override for the session. |
initial_message | no | First prompt, sent as soon as the session is ready. |
system_instructions | no | Custom additions to the system prompt. |
metadata | no | Caller-defined key-value pairs for your own tracking. |
idempotency_key | no | A 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'sstart_idtonext_start_id. Paging is strictly forward (id > start_id). - Stream
GET …/events?stream=truereturns atext/event-stream. Resume a dropped stream withLast-Event-Id. stream_epochidentifies the current run of the session. If its value changes between reads, the session restarted and renumbered its events; re-sync fromstart_id=0.
A status of idle means the current turn has finished.
Retention
- A
PAUSEDsession 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-zipbefore 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
- C3 AI Agentix Developer Docs — a task guide that authenticates and runs one session end to end.
- Quick Start
- Roles and Permissions
- Connections