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.
| Credential | How to send it | Used by |
|---|---|---|
| End-user JWT | Authorization: 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 session | Authorization: Bearer <session-token>, obtained by signing in to the console. | /admin/*, POST /agent/reload, POST /agent/token |
| Admin API key | X-Admin-API-Key: <key> (or as a bearer token). Set by ADMIN_API_KEY. For CI and scripts. | /admin/*, POST /agent/reload |
| Channel key | X-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
| Status | Meaning |
|---|---|
400 | Invalid body or parameters |
401 | Missing, malformed, expired, or wrong-type credential |
403 | Authenticated but not allowed (for example a role outside a channel's allowed_roles) |
404 | Unknown app, definition, or session |
429 | Per-user chat rate limit exceeded. Wait Retry-After, or raise CHAT_RATE_LIMIT_PER_MIN. |
503 | JWT_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.
| Field | Type | Required | Description |
|---|---|---|---|
agentKey | string | yes | Operational agent key (embed_key). appSlug is a deprecated alias. |
role | string | yes | A role key, or a role_mapping key. |
userId | string | no | End-user id. Default playground-<role>. |
agentId | string | no | Actor id that row filters scope to. Default userId. |
tenantId | string | no | Default is the definition's tenant. Ignored for channel keys, which pin it. |
branchId | string | no | Branch-level scoping. |
extra | object of strings | no | Extra claims for {{user.X}} row-filter interpolation. |
ttlSeconds | integer, 60 to 2592000 | no | Default 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.
| Field | Type | Required | Description |
|---|---|---|---|
agentKey | string | yes | Operational agent key (embed_key). appSlug is a deprecated alias. |
message | string | yes | The user message. |
history | array | no | Prior messages, each with role and content. |
sessionId | string | no | Continue an existing session. |
confirmationToken | string | no | Reply to a pending confirmation inline. To submit edited values, use /agent/confirm. |
Response (ChatResponse):
| Field | Type | Description |
|---|---|---|
reply | string | Plain-text reply. |
sessionId | string | Session id for continuity. |
history | array | Updated conversation history. |
blocks | array | Optional structured content: text, table, or form blocks. |
confirmationRequired | object | Set when a mutation awaits approval: toolId, description, params, confirmationToken. |
usage | object | inputTokens and outputTokens. |
toolsUsed | array of string | Tool ids called this turn. |
partial | boolean | True if the loop hit orchestration.max_steps before finishing. |
stepsCompleted | number | Loop iterations completed. |
handedOff | boolean | True if the conversation was escalated to a human. |
referenceId | string | Reference number for a handed-off session. |
thoughts | array | The 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.
| Field | Type | Required | Description |
|---|---|---|---|
agentKey | string | yes | Operational agent key (embed_key). appSlug is a deprecated alias. |
confirmationToken | string | yes | The token from confirmationRequired. |
confirmed | boolean | yes | true runs the stored tool call, false discards it. |
sessionId | string | no | The session the confirmation belongs to. |
editedParams | object | no | Values 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.
| Field | Type | Required | Description |
|---|---|---|---|
agentKey | string | yes | Operational 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:
| Message | Description |
|---|---|
{"type":"chat","message":"...","sessionId":"...","history":[...]} | Run a turn. Only one turn at a time per connection. |
{"type":"ping"} | Keep-alive. |
Server events:
| Event | Description |
|---|---|
ready | Connection accepted, with sessionId. |
text_delta | A chunk of reply text. |
block | A table or form, the moment it is ready. |
thought | A step of what the agent is doing. |
text_reset | The text streamed so far was a pre-tool preamble. Clear it. |
done | The final response, in the /agent/chat shape. |
error | Has code (bad_request, busy, rate_limited, turn_failed), message, and retryAfter when rate limited. |
pong | Reply 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.
| Field | Type | Required | Description |
|---|---|---|---|
agentKey | string | yes | Operational agent key (embed_key). appSlug is a deprecated alias. |
sessionId | string | no | Reuse 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.
| Field | Type | Required | Description |
|---|---|---|---|
content | string | yes | Raw 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 path | Purpose |
|---|---|
POST /admin/definitions/:id/versions | Snapshot the current content. Optional body label. |
GET /admin/definitions/:id/versions | List versions, newest first. |
GET /admin/definitions/:id/versions/:versionId | Full content of one version. |
POST /admin/definitions/:id/versions/:versionId/compare | Line diff. Body against (another version id, or current) and optional draftContent (only with current). |
POST /admin/definitions/:id/versions/:versionId/restore | Make a version the current content. |
DELETE /admin/definitions/:id/versions/:versionId | Permanently 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 path | Query | Returns |
|---|---|---|
GET /admin/sessions | app, userId, status, limit (default 50), sinceDays, workspaceId | { "sessions": [...] } |
GET /admin/sessions/:id | none | { "session": {...} } |
GET /admin/sessions/:id/traces | none | { "traces": [...] } in order |
GET /admin/handoffs | app, limit (default 100), workspaceId | { "sessions": [...] } handed off to a human |
GET /admin/traces | app, userId, type, limit (default 100), workspaceId | { "traces": [...] } |
GET /admin/stats/overview | workspaceId, projectId | Sessions by window, messages, tokens, cost |
GET /admin/stats/runtime | none | Runtime 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 path | Body or query | Purpose |
|---|---|---|
POST /admin/evals/run | filename | Run one definition's cases and return the report. |
POST /admin/evals/runs | filename | Start a run and return before cases finish. |
GET /admin/evals/runs/:id | none | Poll a run for the cases finished so far. |
POST /admin/evals/run-all | workspaceId (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 path | Body | Purpose |
|---|---|---|
GET /admin/arch/:id | none | Deterministic review: health score and failing checks. No model call, safe to poll. |
POST /admin/arch/:id | question (optional) | Same findings plus a model-written analysis. Read-only. |
POST /admin/arch/:id/propose-edit | request (required), baseYaml (optional) | Returns proposed YAML, its health score, and a line diff. Nothing is saved. |
POST /admin/arch/:id/apply-edit | yaml (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 path | Body | Purpose |
|---|---|---|
POST /admin/arch/sessions | tenant (required), workspaceId, projectName, description | Start a session. |
GET /admin/arch/sessions | none | List sessions, newest first. |
GET /admin/arch/sessions/:id | none | Full session state. |
PATCH /admin/arch/sessions/:id | projectName, description | Rename or edit. |
DELETE /admin/arch/sessions/:id | none | Delete the session and its uploaded documents. |
POST /admin/arch/sessions/:id/documents | multipart upload | Upload up to 10 SOP documents (.pdf, .docx, .md, .txt, 10 MB each). |
GET /admin/arch/sessions/:id/documents | none | List documents and parsing status. |
POST /admin/arch/sessions/:id/extract-context | none | Extract structured context from parsed documents. |
POST /admin/arch/sessions/:id/test-connection | connectionString | Test a Postgres connection without saving it. |
POST /admin/arch/sessions/:id/connect-db | connectionString, datasourceId, connectionEnv | Connect and discover the schema. connectionEnv is a variable name, not a value. |
POST /admin/arch/sessions/:id/confirm-tables | tableNames (array) | Choose which tables the agent may read. |
POST /admin/arch/sessions/:id/interview | message (omit on first call) | One interview turn. |
POST /admin/arch/sessions/:id/interview/rewind | stage | Reopen a stage and discard it and later ones. |
POST /admin/arch/sessions/:id/blueprint | none | Build the review document from the interview. |
POST /admin/arch/sessions/:id/blueprint/accept | accepted (boolean), feedback | Accept, or record requested changes. |
POST /admin/arch/sessions/:id/generate | none | Generate the YAML, with up to 3 self-healing attempts. |
GET /admin/arch/sessions/:id/yaml | none | The generated YAML. |
PUT /admin/arch/sessions/:id/yaml | yaml | Edit manually and re-score, with no model call. |
POST /admin/arch/sessions/:id/deploy | none | Publish the YAML and reload the runtime. |
POST /admin/arch/sessions/:id/evals | none | Run 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.
| Area | Paths |
|---|---|
| Channels | /admin/channels. See Widget and Channels. |
| Datasource credentials | /admin/datasources. See Datasources. |
| Audit log | GET /admin/audit, GET /admin/audit/actions |
| Debug context | POST /admin/debug/context. Accepts an operator session only, never the admin key. |
| Workspaces and projects | See Workspaces. |