Error codes
The REST error envelope and the full code-to-status table.
Every error from the REST API uses one envelope. Branch on code; message is human-readable and may
change.
{
"error": {
"code": "CALL_NOT_FOUND",
"message": "Call not found",
"requestId": "req_2f8c1e94-3b6a-4d20-9a7e-1c5f0b8e2d44"
}
}The requestId matches the X-Athos-Request-Id response header — quote it in support tickets.
A few errors also carry a details object with machine-readable extras. It is present only on the
codes that document one (today AGENT_ALREADY_EXISTS); treat it as absent otherwise.
Codes
code | HTTP | Meaning |
|---|---|---|
INVALID_REQUEST | 400 | The request body or query was invalid (validation, bad cursor). |
DRILL_NOT_FOUND | 400 | The drillKey doesn't match a known drill. |
INVALID_API_KEY | 401 | Missing or invalid API key. |
API_KEY_REVOKED | 401 | The API key was revoked. |
INVALID_TOKEN | 401 | The session token was malformed or rejected. |
TOKEN_EXPIRED | 401 | The session token expired before it was redeemed. |
TOKEN_ALREADY_USED | 401 | The single-use session token was already redeemed. |
IP_NOT_ALLOWED | 403 | The caller's IP is not on the allowlist (if one is configured). |
TENANT_INACTIVE | 403 | Your tenant is inactive. |
TENANT_QUOTA_EXCEEDED | 403 | Your tenant's monthly usage allowance is spent. Not retryable: it clears at the start of the next calendar month, or when Athos raises the allowance. |
CALL_NOT_FOUND | 404 | No such call for your tenant (also returned for another tenant's call). |
AGENT_NOT_FOUND | 404 | No agent of yours is registered under that id — the id you minted with, or the agentId you filtered GET /v1/calls by. Register the rep first, or resolve an NPN or email to an id with GET /v1/agents. |
AGENCY_NOT_FOUND | 404 | No such agency for your tenant (also returned for another tenant's agency) — the agencyId / platformAgencyId on POST /v1/agents, GET /v1/agents?agencyId= and GET /v1/calls?agencyId=. Create the agency with POST /v1/agencies first, or check the id you stored. |
AGENT_ALREADY_EXISTS | 409 | A rep is already registered under one of the handles you sent. details carries { agentId, agencyId, matchedOn } — use those ids instead of retrying. |
INTERNAL_ERROR | 500 | An unexpected error. Retry; if it persists, contact Athos with the requestId. |
SERVICE_UNAVAILABLE | 503 | No persona is available for the drill right now. Transient, but the attempt spent the session token — see below. |
Which codes you'll see where
POST /v1/agencies,POST /v1/agentsandGET /v1/agents(roster):INVALID_REQUEST(validation — including a registration that names no agency, or names it both ways; more than one lookup handle onGET /v1/agents; alimitoutside 1–100; a badcursor),INVALID_API_KEY,API_KEY_REVOKED,TENANT_INACTIVE,IP_NOT_ALLOWED,AGENCY_NOT_FOUND(anagencyIdorplatformAgencyIdthat is not one of your agencies — onPOST /v1/agentsandGET /v1/agents?agencyId=;POST /v1/agenciesnever answers it, an unknown id is a create), andAGENT_ALREADY_EXISTS(register only). A lookup that matches nobody is a200with an emptydata, never an error.POST /v1/session(mint):INVALID_REQUEST,INVALID_API_KEY,API_KEY_REVOKED,TENANT_INACTIVE,IP_NOT_ALLOWED,TENANT_QUOTA_EXCEEDED(no token is issued; not retryable until the allowance resets),AGENT_NOT_FOUND(the id is not a registered rep'splatformAgentIdor AthosagentId— an NPN or an email address lands here too).POST /v1/roleplay/session(SDK redeem):INVALID_REQUEST,DRILL_NOT_FOUND,INVALID_TOKEN,TOKEN_EXPIRED,TOKEN_ALREADY_USED,SERVICE_UNAVAILABLE(no persona is available for the drill right now),INTERNAL_ERROR. These surface through the SDK'serrorevent. Most of them spend the session token — see below.GET /v1/callsandGET /v1/calls/:callId(reads):INVALID_REQUEST(list only — a missing or unknownsource, alimitoutside 1–100, a badcursor, or the same rep / agency named both ways),INVALID_API_KEY,API_KEY_REVOKED,TENANT_INACTIVE,IP_NOT_ALLOWED,AGENCY_NOT_FOUND/AGENT_NOT_FOUND(list only — anagencyId/agentIdthat is not yours), andCALL_NOT_FOUND(detail only). TheplatformAgencyId/platformAgentIdfilters never error: an unknown value is an empty page.
Every endpoint can also answer 500 INTERNAL_ERROR.
A failed redemption usually spends the token
The session token is single-use, and it is consumed as the request is redeemed — before the
call is set up. So any failure raised after that point (DRILL_NOT_FOUND, SERVICE_UNAVAILABLE,
INTERNAL_ERROR) leaves the token spent: replaying the identical request returns
TOKEN_ALREADY_USED, even once the underlying condition has cleared. Recover by minting a new
session token and starting a new call.
The SDK gates on the microphone before it redeems, so a refused or missing microphone usually
fails without ever reaching this endpoint and leaves the token unspent. The same codes can also come
from later in the call setup, though, so treat a microphone failure as "retry this token once, mint
a new one if it comes back TOKEN_ALREADY_USED".
Of the codes this endpoint itself returns, only three are raised before redemption and leave the
token unspent: INVALID_TOKEN, TOKEN_EXPIRED and INVALID_REQUEST. Of those, only INVALID_REQUEST is worth retrying with the
same token — a rejected or expired token will not be accepted on a second attempt either. Treat
every other failure as having spent the token.