AvaClone

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:

FieldTypeMeaning
photofileA front-facing image (JPEG or PNG). Or pass image_id for a Studio image instead.
image_idstringA Studio image id to build from.
namestringDisplay name, up to 80 characters.
real_personyes | noWhether 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_adultstring, string, yesThe 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.

FieldTypeMeaning
kindagent | stream | previewagent: the published agent answers. preview: the draft (for your own testing). stream: no agent; you send speech audio and the avatar lip-syncs.
agent_idstringFor agent and preview.
avatar_idstringFor 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.