REST API
Everything the dashboard does, from your server. JSON over HTTPS. Base URL https://app.avaclone.ai/api/v1.
Authentication
Create a secret key under Settings. Send it as a bearer token. Keys are shown once, stored hashed, and can be revoked at any time. They belong on servers only; browsers use an embed’s publishable id instead.
curl https://app.avaclone.ai/api/v1/organisation \
-H "Authorization: Bearer ak_live_…"Errors are JSON with a stable code and a human message:
{ "code": "minutes.exhausted", "message": "No minutes left this period." }Status codes: 400 invalid body, 401 no or bad key, 403 the key or role may not do this, 404 not yours or gone, 409 state (an unpublished agent, an avatar still building), 429 a plan cap, 402 a plan limit such as seats.
Organisation
GET /organisation
Plan, limits and the live minute balance.
{ "id": "org_…", "name": "Harbor Dental", "plan": "pro", "plan_label": "Pro",
"minutes_included": 750, "minutes_left": 512.3, "minutes_used": 237.7, "minutes_available": 1884.3,
"live_at_once": 3, "avatars_max": 3, "embed_domains_max": 5, "premium_multiplier": 2,
"studio_minutes": { "standard": 1, "high": 2, "max": 3 },
"extra_minute_usd": 0.29, "spending_cap_usd": 398, "extra_minutes_allowed": 1372, "extra_minutes_used": 0, "extra_cost_usd": 0,
"period_start": "2026-10-01T00:00:00.000Z" }PATCH /organisation
Owners and admins set the spending cap for extra minutes. { "spending_cap_usd": 200 }; 0 stops at the allowance.
Avatars
POST /avatars
Build an avatar from a photo. multipart/form-data:
| Field | Type | Meaning |
|---|---|---|
| photo | file | A front-facing image (JPEG or PNG). Or pass image_id for a Studio image instead. |
| image_id | string | A Studio image id to build from. |
| name | string | Display name, up to 80 characters. |
| real_person | yes | no | Whether the photo shows a real person (default yes). A real person needs the release fields below; a generated face does not. |
| person_name, person_email, is_adult | string, string, yes | The release: who is depicted, where to send the signed consent, confirmation of age. |
Returns 201 with the avatar in status building. Poll GET /avatars/:id until ready (about a minute on a warm engine) or failed with an error.
GET /avatars
Your avatars with status, engine (lite or premium) and the idle loop URL once ready.
GET /avatars/:id
One avatar.
DELETE /avatars/:id
Remove it. Agents using it stop starting sessions until they are given another.
Agents
POST /agents
{ "name": "Front desk", "avatar_id": "ava_…" }. Creates a draft with sensible defaults (a stock voice, mixed grounding, vision on, 40 turns an hour, 400 a day, a two-minute quiet cut-off).
GET /agents
All agents with their draft and publish state.
GET /agents/:id
One agent: the draft configuration flattened, status (draft or published), published_version, draft_changed.
PATCH /agents/:id
Change the draft. Any subset:
{ "name": "Front desk",
"avatar_id": "ava_…", "voice_id": "stock_warm_f",
"charter": {
"purpose": "Answer questions about the practice and book appointments by phone.",
"audience": "Patients and people choosing a dentist.",
"tone": "Warm, brief, plain.",
"greeting": "Hi, I'm the Harbor Dental assistant. What can I help with?",
"topics": ["hours", "insurance", "services"],
"boundaries": ["No medical advice; suggest a visit."],
"unknown_handling": "Say you are not sure and offer the phone number.",
"facts": ["Open Monday to Friday 8 to 5."],
"sample_qa": [{ "q": "Do you take Delta Dental?", "a": "Yes, we are in network." }]
},
"grounding": "knowledge_only",
"vision": true, "expressive_voice": false,
"limits": { "turns_per_hour": 40, "turns_per_day": 400, "quiet_cutoff_s": 120 } }grounding: knowledge_only answers only from your files and facts; mix adds general knowledge; model_only ignores files.
DELETE /agents/:id
Remove the agent and its embed.
POST /agents/:id/publish
Freeze the draft as the next version. Visitors get it at once; live sessions finish on the version they started with.
GET /agents/:id/versions
Every published version with its configuration.
POST /agents/:id/versions
{ "version": 3 } rolls back: that version becomes the published one and the draft.
Knowledge
POST /agents/:id/knowledge
multipart/form-data with file (PDF, text or Markdown, up to 10 MB). The file is chunked and embedded in the background; GET shows its status.
GET /agents/:id/knowledge
Files with status (indexing, ready, failed) and chunk counts.
DELETE /agents/:id/knowledge?file=kf_…
Remove a file and its chunks.
Embeds
GET /agents/:id/embed
The publishable id, allowed domains, appearance and ready-to-paste snippets:
{ "agent_id": "agt_…", "publishable_id": "pub_…",
"allowed_domains": ["harbordental.com", "www.harbordental.com"],
"appearance": { "position": "bottom_right", "launcher_size": "medium", "launcher_label": "Talk to us", "theme": "auto", "accent": "#0052FF", "captions": true, "background": "avatar" },
"snippets": { "script": "<script src=…>", "inline": "…", "iframe": "<iframe …>", "hosted_url": "https://app.avaclone.ai/a/pub_…" } }PATCH /agents/:id/embed
{ "allowed_domains": ["…"], "appearance": { … } }. Domain count is per plan.
POST /agents/:id/embed/rotate
A new publishable id; the old one stops working at once.
Sessions
POST /sessions
Start a conversation for your own UI (the SDK). Checks minutes and the live cap first.
| Field | Type | Meaning |
|---|---|---|
| kind | agent | stream | preview | agent: the published agent answers. preview: the draft (for your own testing). stream: no agent; you send speech audio and the avatar lip-syncs. |
| agent_id | string | For agent and preview. |
| avatar_id | string | For stream. |
{ "id": "ses_…", "ws_url": "wss://rt.avaclone.ai/v1/sessions/ses_…", "token": "…",
"expires_at": "2026-10-07T21:10:00.000Z", "idle_loop_url": "https://app.avaclone.ai/m/ses_…/idle", "packed": true,
"engine": "lite" }Give ws_url, token and idle_loop_url to the browser. The token is single-use and expires in ten minutes.
GET /sessions/:id
Status (created, live, ended), seconds live, minutes charged, turns, how it ended.
POST /sessions/from-embed
What the widget calls; no key, checked against the embed’s domains by the request’s Origin. { "publishable_id": "pub_…", "visitor_ref": "optional id" }. Same response as above plus the greeting, the appearance and whether the branding line shows.
Studio images
POST /images/generate
{ "prompt": "…", "quality": "standard" | "high" | "max", "count": 1 }. Draws 1, 2 or 3 minutes per image. Returns a job; poll GET /images/jobs/:id for image_ids.
POST /images/:id/edit
{ "prompt": "make the background a bright office", "quality": "high" }. Edit with words; lineage is kept.
POST /images/:id/style
{ "style": "cartoon", "quality": "high" }. Style ids from GET /styles.
POST /images/:id/background
{ "background": "remove" } for a transparent PNG, or { "background": "replace", "background_value": "a bright office" }.
GET /images?library=1
Your images; library=1 only the ones saved to the library.
PATCH /images/:id
{ "saved": true, "name": "…" }.
GET /images/:id/download
The file as a download.
DELETE /images/:id
Remove it.
GET /styles
The style catalogue (id, name, description, version) and the minutes per quality.
GET /voices
Stock voices: Harper, June, Elliot, Theo.
Keys and team
POST /api-keys
{ "name": "production server" }; the secret is in the response once.
GET /api-keys
Names, prefixes, last use.
DELETE /api-keys/:id
Revoke.
GET /team
Members, open invites and seats (signed-in people only).
POST /team
{ "email": "…", "role": "member" | "admin" }; returns the one-time invite link.
Realtime protocol
Over the session WebSocket, text frames are JSON messages (text, end, interrupt, camera, still up; ready, session, state, transcript, look, flush down) and binary frames carry 24 kHz PCM up and packed chunks of JPEG frames with PCM down. The SDK implements it; the format is documented in the SDK source for anyone building their own client.