Reference
Every endpoint of the Atmee API — sessions and avatars — with request and response fields and error codes.
Base URLs
| Environment | Base URL |
|---|---|
| Production | https://api.atmanity.us |
| Staging | https://staging.atmanity.us |
Keys belong to one environment; a staging key is rejected in production and vice versa.
Authentication and scopes
Send your secret in the X-Api-Key header (sk_atmee_…; keys created before September 2026
start with sk_live_… and keep working). Requests are server-to-server only: browsers are
blocked by CORS by design, so the header can never be sent from a page.
Each endpoint below names the scope it needs. A key without it answers 403 insufficient_scope.
An avatar-scoped key can only address the avatar it is scoped to; any other avatarId answers
403 avatar_not_allowed (or a bare 403 on /v1/session).
Amounts are in microcredits: 1 credit = 100 microcredits.
Error shapes
| Status | Body |
|---|---|
400 403 404 409 503 | { "error": "<code>", "message": "..." } — the error codes are listed per endpoint. |
402 | { "error": "<code>", "message": "...", "resetsAt": "<date-time>" } — resetsAt only for windowed caps. |
422 | { "code": 422, "message": "..." } — the body failed schema validation; the message names the field. |
401 413 | No body. |
POST /v1/session
Scope sessions. Starts a session for a visitor of yours; responds only once the avatar has
joined the room.
Request
| Field | Type | Notes |
|---|---|---|
avatarId | string (uuid), required | An avatar your account owns; for a scoped key, the one it is scoped to. |
maxDurationSeconds | integer 1–14400 | Shortens the session below the key/plan/balance limit. Never lengthens it. |
language | string, BCP-47 (≤35 chars) | Per-session language, e.g. en, zh-TW, ja. Omit to use the avatar's own language. |
metadata | object, ≤50 keys | Stored on the session for your own bookkeeping. |
location | object, ≤20 keys | Optional location metadata. |
language is honoured end to end for en, zh-TW and ja today. Other well-formed tags are
accepted and forwarded; speech recognition then falls back to auto-detection.
Response 201
| Field | Type | Notes |
|---|---|---|
sessionId | string (uuid) | Identifies the session in your analytics. |
serverUrl | string | Bare LiveKit URL — pass straight to Room.connect(). |
roomName | string | Informational; the token already selects the room. |
userToken | string | LiveKit access token for the visitor's browser. Expires with the session. |
actualDurationSeconds | integer | The limit actually granted, after key, plan and balance caps. |
Errors
| Status | error | Meaning |
|---|---|---|
401 | — | Missing, malformed, unknown, revoked or wrong-environment key. |
402 | insufficient_credits | Your account balance cannot cover the minimum session. |
402 | api_key_spend_cap_reached | This key's spend cap (total / monthly / daily) is used up. |
402 | concurrent_session_limit | The key or plan already has its maximum sessions running. |
402 | sponsor_budget_exhausted | The owner funds this avatar's visitor chats from Studio and that budget is empty. |
403 | — | The avatar is not on this key's allow-list (scoped key, or another account's avatar). |
422 | — | Schema validation failed (see the field limits above). |
503 | — | A transient error occurred or the service is out of capacity. Retry with backoff. |
There is no key-authenticated "end" call: a session ends when the visitor's participant leaves
the room (room.disconnect()), or when actualDurationSeconds elapses. Billing stops at that
moment.
GET /v1/avatars
Scope avatars:read. Lists your account's avatars, newest first.
| Query | Type | Notes |
|---|---|---|
limit | integer 1–100 | Page size, default 50. |
before | string (date-time) | Cursor: return avatars created before this instant. |
Response 200: { "items": [Avatar…], "nextBefore": "<date-time>" | null }. Pass nextBefore
as the next before until it is null. Each item:
| Field | Type | Notes |
|---|---|---|
id | string (uuid) | |
name | string | |
visibility | private unlisted public | |
status | building ready failed moderation_hold | Overall build state. |
readyToChat | boolean | true once every stage completed and moderation passed. |
moderationState | string, nullable | e.g. clean. |
creationSource | string, nullable | api for avatars created here. |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Errors: 401, 403 insufficient_scope.
POST /v1/avatars
Scope avatars:write; account-wide key only. Creates an avatar from a zip or from asset URLs —
see Create an avatar for the zip layout and manifest.
Body, one of:
multipart/form-datawith onefilepart (the zip, ≤80 MB), or the raw zip asapplication/zip.application/json: the manifest object plus anassetsblock naming the files by publichttpsURL —image: {url},voice: {url}, optionalprompt: {url}(the scenario text, mutually exclusive withpersona.scenario) andknowledge: [{url, description?}](≤20). The service downloads them (same size and type rules as the zip folders) and continues identically. Private, loopback and link-local hosts are refused.
Response 202:
| Field | Type | Notes |
|---|---|---|
avatarId | string (uuid) | |
status | building | |
statusUrl | string | Path of GET /v1/avatars/{avatarId}. |
| Status | error | Meaning |
|---|---|---|
400 | invalid_zip | Not a zip, or it breaks a guard (>80 MB, >40 entries, symlinks, ../, absolute paths). |
400 | invalid_manifest | avatar.json failed validation, or a media folder holds the wrong number or type of files; the message names the problem. |
400 | name_invalid | name is empty or too long. |
400 | public_visibility_not_allowed_on_create | Create with private or unlisted; make it public with PATCH once ready. |
400 | invalid_url | JSON create: an asset URL is not https, carries credentials, or points at a non-public host. |
400 | fetch_failed | JSON create: an asset URL could not be downloaded (unreachable, non-2xx, too many redirects). |
400 | invalid_upload | JSON create: a downloaded file has the wrong type or exceeds its size limit. |
401 | — | Bad key. |
403 | insufficient_scope avatar_not_allowed | Missing scope, or an avatar-scoped key. |
409 | avatar_limit_reached | Per-account cap on API-created avatars (50). |
413 | — | The zip exceeds the size limit. |
503 | avatar_import_disabled | Avatar creation through the API is switched off. Retry later. |
GET /v1/avatars/{avatarId}
Scope avatars:read. The avatar with its per-stage build status — poll this after a create or a
part replacement.
Response 200: every field of a list item plus:
| Field | Type | Notes |
|---|---|---|
description | string, nullable | |
stages | object | appearance, voice, persona, knowledge, previews, each { status, userFeedback }. |
latestFailure | object, nullable | { stage, userFeedback } of the most recent failed stage, if any. |
A stage status is waiting → pending → processing → completed, or failed with a
userFeedback text you can show to the person who supplied the file. stages.previews reads
skipped for avatars created without preview clips (the default; see previews below).
| Field | Type | Notes |
|---|---|---|
previewUrls | object, optional | { idle, talking, expiresAt } — pre-signed mp4 downloads of the finished preview clips, valid about one hour. Present on GET only, once at least one clip is completed; re-GET to refresh. Absent while building, when previews are skipped, and on list/PATCH responses. |
The API does not return share URLs.
Errors: 401, 403 insufficient_scope / avatar_not_allowed, 404 avatar_not_found.
PATCH /v1/avatars/{avatarId}
Scope avatars:write. Updates text fields; send at least one, unknown fields are rejected.
| Field | Type | Notes |
|---|---|---|
name | string 1–100 | |
description | string ≤280 | |
visibility | private unlisted public | public only once the avatar is ready. |
language | en zh-TW ja | |
previewMessage | string ≤500 | |
previews | boolean | true turns preview clip generation on (the preview stages build, previewUrls appear); false turns it off. Each generated clip is billed at the plan's preview price (free on the standard plans today). |
personality | string 1–10000 | Who the avatar is. |
scenario | string 1–10000 | What it is doing with the visitor. |
greeting | string ≤500 | Opening line. |
selfIntroduction | string 51–2000 | |
guardrails | string[] ≤20 items | ≤500 chars each. |
conversationalHabits | object | Habit → CONVERSATIONAL_HABIT_SCALE_LOW / _MEDIUM / _HIGH. |
preferredAlias | string ≤100 | |
spokenName | string ≤100 | |
knownAliases | string[] ≤20 items | 1–100 chars each. |
Response 200: the same object as GET /v1/avatars/{avatarId}. Persona changes re-run the
persona stage. Errors: 400 (validation failure), 401, 403, 404 avatar_not_found.
DELETE /v1/avatars/{avatarId}
Scope avatars:write; account-wide key only. Soft-deletes the avatar; running sessions end
normally. Response 204. Errors: 401, 403, 404 avatar_not_found.
PUT /v1/avatars/{avatarId}/appearance
Scope avatars:write. Replaces the portrait. Body: multipart/form-data with one file part
(jpg/png/webp, ≤10 MB, whole head with margin). Response 200 { "avatarId", "status": "building" }; the appearance and preview stages re-run.
| Status | error | Meaning |
|---|---|---|
400 | invalid_upload | Missing part, unsupported type, or the file fails validation. |
413 | — | The upload exceeds the size limit. |
Plus 401, 403, 404 avatar_not_found as above.
PUT /v1/avatars/{avatarId}/voice
Scope avatars:write. Replaces the voice sample. Body: one file part (mp3/wav/m4a/aac/ogg/
flac/opus/webm, 10–60 s of clean speech, ≤12 MB). Response and errors as for /appearance; the
voice and preview stages re-run.
PUT /v1/avatars/{avatarId}/knowledge
Scope avatars:write. Replaces the whole knowledge set. Body: repeated files parts
(pdf/docx/doc/txt/md/html/csv, ≤50 MB each, ≤20 files) and an optional descriptions part — a
JSON object mapping filename → description (≤500 chars). Sending no files clears the knowledge
base. Response and errors as for /appearance; the knowledge stage re-runs.
Endpoints you will see but should not call
POST /v1/anonymous_session powers the logged-out share link of avatars whose owner funds
visitor chats from Studio. It is called by Atmee's own pages, is rate-limited per IP, and is
not an integration surface — use /v1/session with a key instead.
