C3 AI Documentation Home

C3 AI Agentix Developer Docs

The Agent REST API lets your own code drive an Agentix agent outside of the C3 AI Agentix application UI. Programmatic callers such as CI jobs, batch workflows, and other C3 applications can create an agent session, send a prompt, read the streamed answer, exchange files, and manage the session lifecycle, all over HTTP under a versioned /agent-api/v1/ surface.

This page is a task guide: authenticate, then run one session end to end.

How the API is shaped

The surface is split across two planes, and every integration works with 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 isn't on the per-message path, so throughput scales with the number of pods instead of a single app.

The browser can't call the pod directly

A pod validates a caller's C3 bearer token, but its cross-origin rules don't allow an Authorization header from another web origin, and a browser event stream can't set that header at all. So a browser-based integration must send every pod call through its own server-side proxy, which holds the token and forwards to C3 AI Agentix. A server or command-line caller has no such restriction and calls the pod directly. A chat page inside a C3 application on the same cluster host avoids the problem entirely: see Embed a chat in a C3 app.

Before you begin

You need:

  • A reachable C3 AI Agentix environment and its base URL, ending with a slash, for example https://<agentix-host>/<tenant>/nexus/.
  • The Agentix.User role on that environment.

Which further setup you need depends on where your caller runs — see Authenticate.

Authenticate

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

Same environment: exchange your own session token

When your integration's own code already runs as a C3 application in the same environment as C3 AI Agentix, you need no OAuth at all. This is the common case when the caller is "another C3 application." C3 AI Agentix and your app share the same session store, so any caller already authenticated to your app can mint the platform's own self-scoped session token and present it directly:

Command Line
AGENTIX="https://<agentix-host>/<tenant>/nexus"
TOKEN=$(curl -sS -u "$USER:$PASSWORD" -X POST "https://<your-app-host>/<env>/<your-app>/api/8/User/mySessionToken" \
  -H 'Content-Type: application/json' -d '{}' | grep -o '"signedToken":"[^"]*"' | cut -d'"' -f4)
curl -sS "$AGENTIX/agent-api/v1/sessions/list" -H "Authorization: c3auth $TOKEN"

User.mySessionToken is self-scoped: it only ever returns the caller's own token, needs no role grant beyond Agentix.User, and needs nothing registered on the C3 AI Agentix environment. The platform's authenticator accepts Authorization: c3auth <token> the same way it accepts Authorization: Bearer <token>, so every other example on this page works unchanged with either. The token's lifetime tracks your own C3 session; if a call starts returning 401, mint a fresh one the same way.

The two flows below are for a caller in a different environment. Both need one prerequisite first: register an OAuth application on the C3 AI Agentix environment once, scoped to Agentix.User. This issues the client credentials the flow authenticates with. Run this in a console on the C3 AI Agentix environment:

JavaScript
var app = OAuthApplication.make({
  name: 'myIntegration',
  redirectUri: 'https://<your-app-base-url>/',
  homePageUrl: 'https://<your-app-base-url>/',
  description: 'External client for Agentix agents.',
});
var creds = app.register(['Agentix.User']); // returns { clientId, clientSecret }

Capture creds.clientId and store creds.clientSecret securely: the secret is shown once. The redirectUri matters only for the per-user flow below; a headless caller can leave it as the app home URL. On platform 8.10 and later the type is named OAuth.Application; on 8.9 it's OAuthApplication. The rest of this section works the same on both.

Headless caller: client_credentials

Command Line
AGENTIX="https://<agentix-host>/<tenant>/nexus"
curl -sS -X POST "$AGENTIX/oauth/token" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET"
# -> {"access_token":"<token>","token_type":"bearer"}

The client credentials must appear in both the HTTP Basic header (-u) and the form body (-d). Sending them in only one place returns 401. The access token defaults to a two-hour lifetime and has no refresh token, so re-mint it when it expires.

Present the token on every control-plane and data-plane call as an Authorization header:

Text
Authorization: Bearer <access_token>

External signed-in user: authorization_code with PKCE

Use this flow when your caller runs in a different environment, or isn't a C3 application at all. It's for when the agent should act as a specific signed-in person (their C3 AI Agentix roles, their own sessions) instead of as a shared service identity. It runs in three steps, and only the first needs a browser.

  1. Authorize in the browser. Send the user's browser to the C3 AI Agentix authorize endpoint with a random state and a PKCE code_challenge. After the user signs in, C3 AI Agentix redirects the browser back to your registered redirectUri with code and state on the query string.

    Text
    <agentixBaseUrl>/oauth/authorize?response_type=code&client_id=<clientId>
      &redirect_uri=<redirectUri>&scope=&state=<state>
      &code_challenge=<challenge>&code_challenge_method=S256

    The state is an opaque random value you generate and re-check on return, to guard against cross-site request forgery. The code_challenge is the base64url SHA-256 of a random code_verifier that you keep server-side for step 2. The redirect_uri must match the value registered on the OAuth application byte for byte.

  2. Exchange the code for tokens. Your server-side code swaps the returned code, together with the stored code_verifier, for tokens. Send the client credentials in both the Basic header and the body, the same gate as the headless flow.

    Command Line
    curl -sS -X POST "$AGENTIX/oauth/token" \
      -H 'Content-Type: application/x-www-form-urlencoded' -u "$CLIENT_ID:$CLIENT_SECRET" \
      --data-urlencode "grant_type=authorization_code" \
      --data-urlencode "code=$CODE" \
      --data-urlencode "redirect_uri=$REDIRECT_URI" \
      --data-urlencode "code_verifier=$CODE_VERIFIER" \
      --data-urlencode "client_id=$CLIENT_ID" \
      --data-urlencode "client_secret=$CLIENT_SECRET"
    # -> {"access_token":"<token>","token_type":"bearer","refresh_token":"<token>"}
  3. Refresh when the token expires. This flow returns a refresh_token, so you renew the access token without sending the user back through sign-in.

    Command Line
    curl -sS -X POST "$AGENTIX/oauth/token" \
      -H 'Content-Type: application/x-www-form-urlencoded' -u "$CLIENT_ID:$CLIENT_SECRET" \
      --data-urlencode "grant_type=refresh_token" \
      --data-urlencode "refresh_token=$REFRESH_TOKEN" \
      --data-urlencode "client_id=$CLIENT_ID" \
      --data-urlencode "client_secret=$CLIENT_SECRET"

    Present the resulting access token the same way as the headless flow.

Read authentication errors

Two status codes cover every authentication failure, whichever path above you used:

  • 401 means the credential is missing, rejected, or expired. Mint a fresh session token (same environment), re-mint (headless), or refresh (external signed-in user).
  • 403 means the caller lacks the Agentix.User role or doesn't own the session.

Match on the HTTP status, not the response body: the platform doesn't return a machine-readable error code and doesn't distinguish an expired token from a rejected one.

Run a session end to end

This section is one complete round trip with a headless token: create a session, wait for it to be ready, send a prompt, read the reply, exchange files, then stop. Set these first:

Command Line
AGENTIX="https://<agentix-host>/<tenant>/nexus"
TOKEN="<access_token from the previous step>"

1. Create a session

Command Line
curl -sS -X POST "$AGENTIX/agent-api/v1/sessions/create" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"agent_name":"default"}'
# -> 202 { "id": "<sessionId>", "status": "STARTING",
#          "pod_url": "https://<agentix-host>/.../cloud-agent/<sessionId>" }

The response gives you the id (the session) and the pod_url (the data-plane base). A STARTING status means the pod is still warming; a cold start can take a few minutes. The Retry-After header tells you how long to wait before polling. agent_name selects an agent definition; default is the built-in agent, and Discover agents, models, and status lists the rest.

2. Wait until the session is ready

Command Line
SESSION_ID="<id from the create response>"
curl -sS "$AGENTIX/agent-api/v1/sessions/get/$SESSION_ID" \
  -H "Authorization: Bearer $TOKEN"
# -> { "status": "READY", "pod_url": "..." }

Poll sessions/get until status is READY or ACTIVE. Honor the Retry-After header on STARTING responses instead of polling in a tight loop. A terminated or removed session returns 410 Gone.

Capture the pod URL for the data-plane calls that follow:

Command Line
POD_URL="<pod_url from the get response>"

3. Send a message

Command Line
curl -sS -X POST "$POD_URL/agents/v1/sessions/$SESSION_ID/messages" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"prompt":"List the wind turbines with the lowest availability."}'
# -> 202 { "turn_id": "<turnId>" }

Message and event paths on the pod sit under /agents/v1/, not /agent-api/v1/. Sending is asynchronous by default: you get a turn_id and read the result from the event stream in the next step. For a short turn, add ?wait=true (or the header Prefer: wait=<seconds>, up to 300) to hold the connection and get the finished turn inline as 200 { turn_id, status, events }. Only one turn runs per session at a time; a second send while one is running returns 409.

4. Read the agent's reply

Poll the event stream, paging forward by event id:

Command Line
curl -sS "$POD_URL/agents/v1/sessions/$SESSION_ID/events?start_id=0" \
  -H "Authorization: Bearer $TOKEN"
# -> { "events": [ ... ], "next_start_id": 42, "status": "idle", "stream_epoch": "..." }

The event stream is the canonical transcript. Each event has a monotonic id; on the next poll set start_id to the next_start_id you were given. The agent's text lives in events whose kind is MessageEvent, in the content array. A status of idle means the turn has finished.

For an interactive caller, stream the events instead of polling:

Command Line
curl -sS -N "$POD_URL/agents/v1/sessions/$SESSION_ID/events?stream=true" \
  -H "Authorization: Bearer $TOKEN"

With stream=true the pod returns a text/event-stream. Resume a dropped stream with the Last-Event-Id header (or the last_event_id query parameter). If the stream_epoch value changes, the pod restarted and renumbered its events, so re-sync from start_id=0.

5. Exchange files

File operations run against the pod under the /agent-api/v1/ base and work as documented.

OperationCall
ListGET <podUrl>/agent-api/v1/sessions/files/<sessionId>
UploadPOST <podUrl>/agent-api/v1/sessions/files/<sessionId> (multipart, field file)
Preview or viewGET <podUrl>/agent-api/v1/sessions/file-view/<sessionId>?path=<path>
Download one fileGET <podUrl>/agent-api/v1/sessions/file/<sessionId>?path=<path>
Download the workspaceGET <podUrl>/agent-api/v1/sessions/files-zip/<sessionId>
Command Line
# Upload an input file
curl -sS -X POST "$POD_URL/agent-api/v1/sessions/files/$SESSION_ID" \
  -H "Authorization: Bearer $TOKEN" -F "file=@./input.csv"

# Download everything the agent produced
curl -sS "$POD_URL/agent-api/v1/sessions/files-zip/$SESSION_ID" \
  -H "Authorization: Bearer $TOKEN" -o workspace.zip

Running a shell command in the workspace (sessions/<sessionId>/exec) is available only on the hosted OpenHands runtime; 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.

6. List, stop, resume, and delete

Command Line
# List your sessions
curl -sS "$AGENTIX/agent-api/v1/sessions/list" -H "Authorization: Bearer $TOKEN"

# Stop the pod, keeping its files (status becomes PAUSED)
curl -sS -X POST "$AGENTIX/agent-api/v1/sessions/stop/$SESSION_ID" -H "Authorization: Bearer $TOKEN"

# Resume a paused pod
curl -sS -X POST "$AGENTIX/agent-api/v1/sessions/resume/$SESSION_ID" -H "Authorization: Bearer $TOKEN"

# Soft-remove this chat thread
curl -sS -X DELETE "$AGENTIX/agent-api/v1/sessions/delete/$SESSION_ID" -H "Authorization: Bearer $TOKEN"

Stop and resume act on the whole pod, so every session on it pauses or wakes together. Delete removes one chat thread, and you can't delete the last chat on a pod. A paused session keeps its files and transcript on the pod's disk; deleting the session terminates it and loses them, so export anything you need with files-zip first.

Embed a chat in a C3 app

The most common integration is a chat page inside an existing C3 application. Don't build this on the Agent REST API: a consumer app on the same cluster host as C3 AI Agentix drops in the NexusChatEmbed component instead (contact your C3 AI representative for setup details). It needs no server-side piece at all: no proxy, no bearer token to hold or refresh, no redirect URI to register. The chat rides the page's existing sign-in through a one-time silent cookie exchange, so every call runs as the signed-in end user.

NexusChatEmbed replaces the superseded approach, in which a server-side proxy held a bearer token and forwarded every call. Don't reintroduce that proxy for a same-cluster embed.

Discover agents, models, and status

These control-plane calls tell a caller what's available and how its pod is doing.

CallReturns
GET /agent-api/v1/definitionsThe agent definitions you can pass as agent_name on create.
GET /agent-api/v1/modelsThe LLM models you can request for a session.
GET /agent-api/v1/users/meYour identity and your active-session count.
GET /agent-api/v1/statusThe health of your current service, including whether its pod is reachable.

The full machine-readable contract is served live at GET /agent-api/v1/openapi.json and can be viewed with any OpenAPI tool such as Swagger UI or ReDoc.

Troubleshooting

Match the symptom you're seeing to its likely cause and fix:

SymptomLikely cause and fix
401 on every token requestThe client credentials aren't in both the Basic header and the body. Send them in both.
OAuth endpoints return 404agentixBaseUrl is missing its trailing slash.
Bad request after the user signs inThe redirect URI doesn't match the value registered on the OAuth application byte for byte.
state mismatch on the callbackThe state you generated no longer matches or has expired on your side. Retry sign-in.
401 on an API callThe token expired. Re-mint it (headless) or let the refresh run (per-user).
403 on an API callThe caller lacks the Agentix.User role, or the session belongs to another user.
First message hangs or times outA cold start can take a few minutes. Poll sessions/get and honor Retry-After.
429 with a Retry-After headerYou reached the per-pod rate limit or the concurrent-turn cap. Back off for the stated interval.
create, list, or a paused get misbehavesSee the current-build notes in Run a session end to end.

See also

Was this page helpful?