Skip to main content

API Reference

This page is for the engineers who connect your systems to Achilles. Most day-to-day work happens in the console, but everything the console does is available over a REST API: chat, streaming and voice sessions, agent administration, evaluations, and audit data. Use it to embed chat in your own app, automate deployments from a CI pipeline, or pull session data into your reporting tools.

If you are new to integrating, start with the Integration overview, which explains identity, tokens, and the widget. Return here for exact request and response shapes.

The Achilles API serves chat, voice, administration, and health endpoints. The examples below use $API for the base URL.

export API=https://api.example.com

Authentication​

There are four credential types. Each endpoint below names the one it needs. For the full model, see Integration overview.

CredentialHow to send itUsed by
End-user JWTAuthorization: Bearer <jwt>. HS256, signed with JWT_SECRET. Identity (userId, role, tenantId) comes only from the token, never the body./agent/chat, /agent/confirm, chat and voice sessions, /mcp/*
Operator sessionAuthorization: Bearer <session-token>, obtained by signing in to the console./admin/*, POST /agent/reload, POST /agent/token
Admin API keyX-Admin-API-Key: <key> (or as a bearer token). Set by ADMIN_API_KEY. For CI and scripts./admin/*, POST /agent/reload
Channel keyX-Channel-Key: chn_live_.... Created under Deployments in the console.POST /agent/token only

Admin endpoints accept an operator session or the admin API key. Under NODE_ENV=production with no key set, they refuse all requests.

Request bodies are validated strictly. An undeclared property is rejected with 400.

Workspace-scoped paths​

Every /admin/... route is also mounted at /ws/:wsid/admin/.... On that mount the workspace comes from the URL and membership is checked. On the plain mount you can pass ?workspaceId= instead. This page shows the plain paths.

Common errors​

StatusMeaning
400Invalid body or parameters
401Missing, malformed, expired, or wrong-type credential
403Authenticated but not allowed (for example a role outside a channel's allowed_roles)
404Unknown app, definition, or session
429Per-user chat rate limit exceeded. Wait Retry-After, or raise CHAT_RATE_LIMIT_PER_MIN.
503JWT_SECRET is not configured (token minting)

Health​

GET /health​

Liveness plus per-app definition health. Use it as a container healthcheck or deploy gate. No auth.

curl -s $API/health
{
"status": "ok",
"service": "achilles-agent-api",
"timestamp": "2026-01-01T00:00:00.000Z",
"apps": [
{
"slug": "example-support",
"healthScore": 100,
"checksPassed": 17,
"totalChecks": 17,
"deployReady": true,
"toolCount": 2
}
]
}

Agent runtime​

GET /agent/apps​

List the slugs of loaded agents. No auth.

curl -s $API/agent/apps
# {"apps":["example-support"]}

GET /agent/capabilities/:agentKey​

Text and voice capabilities for one agent, used by the widget and the console. No auth.

Response fields: agentKey (canonical), appSlug (deprecated alias), text, greeting, personaName, tenant, roles, roleMappingKeys, and voice (enabled, modes, stt, tts, greeting, speechReady).

curl -s $API/agent/capabilities/example-support

POST /agent/token​

Mint a short-lived end-user JWT. Auth: operator session (the Playground) or channel key (a host app backend). It does not accept the admin key.

FieldTypeRequiredDescription
agentKeystringyesOperational agent key (embed_key). appSlug is a deprecated alias.
rolestringyesA role key, or a role_mapping key.
userIdstringnoEnd-user id. Default playground-<role>.
agentIdstringnoActor id that row filters scope to. Default userId.
tenantIdstringnoDefault is the definition's tenant. Ignored for channel keys, which pin it.
branchIdstringnoBranch-level scoping.
extraobject of stringsnoExtra claims for {{user.X}} row-filter interpolation.
ttlSecondsinteger, 60 to 2592000noDefault 900. Channel keys are capped at 3600.

With a channel key, the channel fixes the app and tenant, and role must be in the channel's allowed_roles, otherwise 403.

Response: token and expiresIn (seconds).

curl -s -X POST $API/agent/token \
-H "X-Channel-Key: $CHANNEL_KEY" -H 'Content-Type: application/json' \
-d '{"agentKey":"example-support","role":"viewer","userId":"user-1","extra":{"customerId":"C-1001"}}'
{ "token": "<jwt>", "expiresIn": 900 }

POST /agent/chat​

Send a message and receive the reply. This is the main agentic endpoint. Auth: end-user JWT. Subject to the per-user rate limit.

FieldTypeRequiredDescription
agentKeystringyesOperational agent key (embed_key). appSlug is a deprecated alias.
messagestringyesThe user message.
historyarraynoPrior messages, each with role and content.
sessionIdstringnoContinue an existing session.
confirmationTokenstringnoReply to a pending confirmation inline. To submit edited values, use /agent/confirm.

Response (ChatResponse):

FieldTypeDescription
replystringPlain-text reply.
sessionIdstringSession id for continuity.
historyarrayUpdated conversation history.
blocksarrayOptional structured content: text, table, or form blocks.
confirmationRequiredobjectSet when a mutation awaits approval: toolId, description, params, confirmationToken.
usageobjectinputTokens and outputTokens.
toolsUsedarray of stringTool ids called this turn.
partialbooleanTrue if the loop hit orchestration.max_steps before finishing.
stepsCompletednumberLoop iterations completed.
handedOffbooleanTrue if the conversation was escalated to a human.
referenceIdstringReference number for a handed-off session.
thoughtsarrayThe steps the agent took. Full detail for Playground tokens, sanitized for end users.
curl -s -X POST $API/agent/chat \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"agentKey":"example-support","message":"What is the status of order ORD-1001?"}'
{
"reply": "Order ORD-1001 has shipped.",
"sessionId": "b6f1c0e2-...",
"toolsUsed": ["get_order_status"],
"usage": { "inputTokens": 812, "outputTokens": 41 }
}

POST /agent/confirm​

Approve or cancel a pending mutation. Auth: end-user JWT. See Confirmations.

FieldTypeRequiredDescription
agentKeystringyesOperational agent key (embed_key). appSlug is a deprecated alias.
confirmationTokenstringyesThe token from confirmationRequired.
confirmedbooleanyestrue runs the stored tool call, false discards it.
sessionIdstringnoThe session the confirmation belongs to.
editedParamsobjectnoValues from a submitted form, overriding extracted params. Only keys the tool declares are used.

Response: the same shape as /agent/chat.

curl -s -X POST $API/agent/confirm \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"agentKey":"example-support","confirmationToken":"<token>","confirmed":true}'

POST /agent/reload​

Reload definitions from the store without restarting. Auth: operator session or admin key. The reload is recorded in the audit log.

curl -s -X POST $API/agent/reload -H "X-Admin-API-Key: $ADMIN_API_KEY"
# {"status":"reloaded","apps":["example-support"]}

Streaming chat​

Browsers cannot set headers on a WebSocket, so streaming uses a two-step ticket flow.

POST /agent/chat/session​

Create a streaming session. Auth: end-user JWT.

FieldTypeRequiredDescription
agentKeystringyesOperational agent key (embed_key). appSlug is a deprecated alias.

Response: sessionId, agentKey (and deprecated appSlug), and ws, a path with a single-use ticket.

curl -s -X POST $API/agent/chat/session \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"agentKey":"example-support"}'
{
"sessionId": "b6f1c0e2-...",
"agentKey": "example-support",
"ws": "/agent/chat/stream?sessionId=b6f1c0e2-...&ticket=..."
}

WebSocket /agent/chat/stream​

Connect to the ws path from the previous call. The ticket is redeemed on connect.

Client messages:

MessageDescription
{"type":"chat","message":"...","sessionId":"...","history":[...]}Run a turn. Only one turn at a time per connection.
{"type":"ping"}Keep-alive.

Server events:

EventDescription
readyConnection accepted, with sessionId.
text_deltaA chunk of reply text.
blockA table or form, the moment it is ready.
thoughtA step of what the agent is doing.
text_resetThe text streamed so far was a pre-tool preamble. Clear it.
doneThe final response, in the /agent/chat shape.
errorHas code (bad_request, busy, rate_limited, turn_failed), message, and retryAfter when rate limited.
pongReply to ping.

Confirmations remain REST-only through POST /agent/confirm.

Voice​

POST /agent/voice/session​

Create a voice session. Auth: end-user JWT. Returns 400 if voice is not enabled for the agent. See Voice.

FieldTypeRequiredDescription
agentKeystringyesOperational agent key (embed_key). appSlug is a deprecated alias.
sessionIdstringnoReuse an existing session.

The response carries one single-use, 60-second ticket per socket.

curl -s -X POST $API/agent/voice/session \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"agentKey":"example-support"}'
{
"sessionId": "b6f1c0e2-...",
"agentKey": "example-support",
"ws": {
"stt": "/agent/voice/stt?sessionId=b6f1c0e2-...&ticket=...",
"orchestration": "/agent/voice/orchestration?sessionId=b6f1c0e2-...&ticket=..."
}
}

WebSocket /agent/voice/stt and /agent/voice/orchestration​

Connect to the two paths returned above. The stt socket carries microphone audio for streaming transcription. The orchestration socket carries the turn events and synthesized speech. Tickets are single-use, so a replayed or forged ticket is rejected.

Definitions​

Auth for all routes in this section: operator session or admin key. Identity is a row id (UUID). A plain filename is accepted on GET only as the on-disk seed fallback when the store is empty.

GET /admin/definitions​

List definitions. Query: workspaceId, projectId.

curl -s "$API/admin/definitions" -H "X-Admin-API-Key: $ADMIN_API_KEY"

Response: { "definitions": [ ... ] }, each with filename, app, version, tenant, persona name, tool and role counts, validity, and error and warning counts.

GET /admin/definitions/:id​

Get one definition with its validation status.

Response: id, embedKey, filename, content (the YAML), source, workspaceId, projectId, valid, errors, warnings, and summary (app, version, tenant, personaName, toolCount, roleCount).

POST /admin/definitions/validate​

Validate YAML without saving. See the Validator.

FieldTypeRequiredDescription
contentstringyesRaw YAML.
curl -s -X POST $API/admin/definitions/validate \
-H "X-Admin-API-Key: $ADMIN_API_KEY" -H 'Content-Type: application/json' \
-d "$(jq -n --rawfile c agent.yaml '{content:$c}')"
{
"valid": false,
"errors": [
{
"path": "tools.get_order_status.sql",
"message": "...",
"severity": "error",
"code": "ACH-SEC-01"
}
],
"warnings": [],
"summary": {
"app": "example-support",
"version": 1,
"tenant": "example",
"personaName": "Ava",
"toolCount": 2,
"roleCount": 2
}
}

POST /admin/definitions​

Create a definition. Invalid content is rejected. Body: content (required), workspaceId (required for an operator session creating a new agent), projectId (optional). Returns the same fields as a save, below.

PUT /admin/definitions/:id​

Replace an existing definition. Same body as create. The runtime reloads after a successful save.

curl -s -X PUT $API/admin/definitions/$ID \
-H "Authorization: Bearer $SESSION_TOKEN" -H 'Content-Type: application/json' \
-d "$(jq -n --rawfile c agent.yaml '{content:$c}')"

Response: id, embedKey, filename, workspaceId, projectId, valid, errors, warnings, summary.

DELETE /admin/definitions/:id​

Delete a definition. Response: { "deleted": true }.

Versions​

Method and pathPurpose
POST /admin/definitions/:id/versionsSnapshot the current content. Optional body label.
GET /admin/definitions/:id/versionsList versions, newest first.
GET /admin/definitions/:id/versions/:versionIdFull content of one version.
POST /admin/definitions/:id/versions/:versionId/compareLine diff. Body against (another version id, or current) and optional draftContent (only with current).
POST /admin/definitions/:id/versions/:versionId/restoreMake a version the current content.
DELETE /admin/definitions/:id/versions/:versionIdPermanently delete a version.
curl -s -X POST $API/admin/definitions/$ID/versions \
-H "X-Admin-API-Key: $ADMIN_API_KEY" -H 'Content-Type: application/json' \
-d '{"label":"Before adding refund tool"}'

Sessions and traces​

Auth: operator session or admin key. See Tracing and Sessions and Evals.

Method and pathQueryReturns
GET /admin/sessionsapp, userId, status, limit (default 50), sinceDays, workspaceId{ "sessions": [...] }
GET /admin/sessions/:idnone{ "session": {...} }
GET /admin/sessions/:id/tracesnone{ "traces": [...] } in order
GET /admin/handoffsapp, limit (default 100), workspaceId{ "sessions": [...] } handed off to a human
GET /admin/tracesapp, userId, type, limit (default 100), workspaceId{ "traces": [...] }
GET /admin/stats/overviewworkspaceId, projectIdSessions by window, messages, tokens, cost
GET /admin/stats/runtimenoneRuntime health figures
curl -s "$API/admin/traces?type=guardrail_deny&limit=20" \
-H "X-Admin-API-Key: $ADMIN_API_KEY"

Evals​

Auth: operator session or admin key. See Evaluation.

Method and pathBody or queryPurpose
POST /admin/evals/runfilenameRun one definition's cases and return the report.
POST /admin/evals/runsfilenameStart a run and return before cases finish.
GET /admin/evals/runs/:idnonePoll a run for the cases finished so far.
POST /admin/evals/run-allworkspaceId (query)Run every definition in scope.
curl -s -X POST $API/admin/evals/run \
-H "X-Admin-API-Key: $ADMIN_API_KEY" -H 'Content-Type: application/json' \
-d '{"filename":"example-support.yaml"}'

Arch (definition review and edit)​

Arch reviews a definition and proposes edits. It never writes without an explicit apply. Auth: operator session or admin key.

Method and pathBodyPurpose
GET /admin/arch/:idnoneDeterministic review: health score and failing checks. No model call, safe to poll.
POST /admin/arch/:idquestion (optional)Same findings plus a model-written analysis. Read-only.
POST /admin/arch/:id/propose-editrequest (required), baseYaml (optional)Returns proposed YAML, its health score, and a line diff. Nothing is saved.
POST /admin/arch/:id/apply-edityaml (required)Save a proposal through the normal validate-then-save gate, then reload. Returns filename, valid, errors, warnings.

Take a version snapshot before applying an edit you might want to undo.

curl -s -X POST $API/admin/arch/$ID/propose-edit \
-H "Authorization: Bearer $SESSION_TOKEN" -H 'Content-Type: application/json' \
-d '{"request":"mask PII for the viewer role"}'

Agent Builder sessions​

The Agent Builder drives a staged flow from documents and a database connection to a deployed definition. See Agent Builder. Auth: operator session or admin key. All routes are under /admin/arch/sessions.

Method and pathBodyPurpose
POST /admin/arch/sessionstenant (required), workspaceId, projectName, descriptionStart a session.
GET /admin/arch/sessionsnoneList sessions, newest first.
GET /admin/arch/sessions/:idnoneFull session state.
PATCH /admin/arch/sessions/:idprojectName, descriptionRename or edit.
DELETE /admin/arch/sessions/:idnoneDelete the session and its uploaded documents.
POST /admin/arch/sessions/:id/documentsmultipart uploadUpload up to 10 SOP documents (.pdf, .docx, .md, .txt, 10 MB each).
GET /admin/arch/sessions/:id/documentsnoneList documents and parsing status.
POST /admin/arch/sessions/:id/extract-contextnoneExtract structured context from parsed documents.
POST /admin/arch/sessions/:id/test-connectionconnectionStringTest a Postgres connection without saving it.
POST /admin/arch/sessions/:id/connect-dbconnectionString, datasourceId, connectionEnvConnect and discover the schema. connectionEnv is a variable name, not a value.
POST /admin/arch/sessions/:id/confirm-tablestableNames (array)Choose which tables the agent may read.
POST /admin/arch/sessions/:id/interviewmessage (omit on first call)One interview turn.
POST /admin/arch/sessions/:id/interview/rewindstageReopen a stage and discard it and later ones.
POST /admin/arch/sessions/:id/blueprintnoneBuild the review document from the interview.
POST /admin/arch/sessions/:id/blueprint/acceptaccepted (boolean), feedbackAccept, or record requested changes.
POST /admin/arch/sessions/:id/generatenoneGenerate the YAML, with up to 3 self-healing attempts.
GET /admin/arch/sessions/:id/yamlnoneThe generated YAML.
PUT /admin/arch/sessions/:id/yamlyamlEdit manually and re-score, with no model call.
POST /admin/arch/sessions/:id/deploynonePublish the YAML and reload the runtime.
POST /admin/arch/sessions/:id/evalsnoneRun the generated definition's own eval cases.

MCP server​

ALL /mcp/:agentKey/:datasourceId​

Exposes an agent's tools on an MCP datasource as a streamable HTTP MCP server, so an external MCP client can call them. Auth: end-user JWT. The server lists only the tools the caller's role may use, and every call runs through the same row filters and guardrails as chat. An unknown datasource returns 404. See Connectors and MCP.

The endpoint speaks JSON-RPC over MCP streamable HTTP.

curl -s -X POST $API/mcp/example-support/support_mcp \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Other admin routes​

The console also uses these admin routes, all under the same auth. They are listed for completeness and are documented alongside their features.

AreaPaths
Channels/admin/channels. See Widget and Channels.
Datasource credentials/admin/datasources. See Datasources.
Audit logGET /admin/audit, GET /admin/audit/actions
Debug contextPOST /admin/debug/context. Accepts an operator session only, never the admin key.
Workspaces and projectsSee Workspaces.