Minting session tokens
Exchange your API key for a short-lived session token the SDK can use.
The browser SDK can't hold your API key, so your backend mints a short-lived, single-use session token for it. Expose this as your own endpoint that your authenticated frontend calls.
A token is minted for a registered agent. Register each rep once with
POST /v1/agents before their first practice call, then
mint with either of the agent's two ids.
POST /v1/session
POST https://app.useathos.ai/api/external/v1/session
Authorization: Bearer ath_live_…
Content-Type: application/jsonRequest body
Exactly one of:
Prop
Type
Sending neither, or both, is 400 INVALID_REQUEST. null is treated as omitted.
The mint takes an id and nothing else — no agency id (the agency is derived from the agent),
and no NPN or email. To mint from an NPN or an email, resolve it to an id first with
GET /v1/agents.
Response 200 OK
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"expiresAt": "2026-06-05T18:05:00.000Z"
}Implement your token endpoint
Wrap POST /v1/session in your own authenticated endpoint so the API key stays on your backend and
you decide which rep the token represents.
curl -X POST https://app.useathos.ai/api/external/v1/session \
-H "Authorization: Bearer $ATHOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"platformAgentId":"rep_8842"}'// Your endpoint, e.g. POST /athos/token
app.post('/athos/token', requireAuth, async (req, res) => {
const r = await fetch('https://app.useathos.ai/api/external/v1/session', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ATHOS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
platformAgentId: req.user.id, // the id you registered the rep under; never client-supplied
}),
});
const body = await r.json();
res.status(r.status).json(body); // { token, expiresAt } on success
});# Your endpoint, e.g. POST /athos/token
import os, requests
@app.post("/athos/token")
@require_auth
def athos_token():
r = requests.post(
"https://app.useathos.ai/api/external/v1/session",
headers={"Authorization": f"Bearer {os.environ['ATHOS_API_KEY']}"},
json={
"platformAgentId": current_user.id, # the id you registered the rep under; never client-supplied
},
timeout=10,
)
return r.json(), r.status_code # { "token": ..., "expiresAt": ... }Always derive the id you mint with from your authenticated session, not from the request body. A token scopes a live call to that rep; letting the client choose it lets one rep impersonate another.
Rules of the road
- Mint at the moment of use. The token lives ~5 minutes and is single-use. Mint it when the rep
clicks "start practice", hand it straight to
AthosRoleplay.create({ token, drillKey }), and connect. - One token per call. Reusing a token returns
TOKEN_ALREADY_USEDat the SDK. - Server-to-server only. This endpoint is not CORS-enabled (no
Access-Control-Allow-Origin), so a browser blocks the response — it must be called from your backend.
Errors
| Status | code | Cause |
|---|---|---|
| 400 | INVALID_REQUEST | Neither or both of agentId / platformAgentId, or a blank one. |
| 401 | INVALID_API_KEY | Bad or missing API key. |
| 401 | API_KEY_REVOKED | The key was revoked — rotate. |
| 403 | TENANT_INACTIVE | Your tenant is inactive. |
| 403 | IP_NOT_ALLOWED | Only if an IP allowlist is configured for your tenant: the request came from an address outside it. |
| 403 | TENANT_QUOTA_EXCEEDED | Your tenant's monthly usage allowance is spent. |
| 404 | AGENT_NOT_FOUND | No agent of yours is registered under that id. Register the rep first, or resolve an NPN or email to an id with GET /v1/agents. |
| 500 | INTERNAL_ERROR | An unexpected error. Retry; if it persists, contact Athos with the requestId. |
TENANT_QUOTA_EXCEEDED issues no token and is not retryable — the allowance lifts at the start of
the next calendar month, or when your Athos contact raises it. It is measured in call minutes across
your whole tenant, practice and real calls alike. Surface it to the
rep as "practice is unavailable this month". Full envelope and list:
error reference.