Create an avatar
Two ways to create an avatar with the API: send a JSON manifest that points at your files, or upload everything in one zip. Either builds Atmee V2 or Atmee V1.
Everything you can do in Studio's Create flow is also an API call. An avatar made through the API
shows up in Studio and starts sessions with POST /v1/sessions like any other.
There are two ways to create one. Both build either model and end the same way.
| With JSON | With a zip | |
|---|---|---|
| Your files | Public https links, or uploads you send first | Inside the zip, next to the manifest |
| One request is | A JSON manifest | A zip, as a multipart file or a raw application/zip |
| Best for | Apps and agents, files already online, Atmee V2 | Files on disk, many avatars at once |
Before you start
- A key with
avatars:write, account-wide: an avatar-scoped key can't create avatars. Addgeneration:writeto generate a portrait or a voice. See API keys. - A portrait: jpg, png or webp, up to 10 MB. See Avatar 101: Negative examples for the photos that animate badly.
- A voice sample: 10 to 60 seconds of one person speaking clearly, up to 12 MB: mp3, wav, m4a, aac, ogg, flac, opus or webm. An Atmee V2 avatar's voice is cloned from it.
- Permission: if the portrait or the voice is a real person other than you, have that person's permission first.
- The model: set
enginetov2orv1on every create. Without it the API builds Atmee V1. Atmee V2 is invite only for now (403 v2_access_requiredwithout access).
Create with JSON
Send a manifest that points at your files. Atmee downloads them, checks them and starts the build.
Get your files to Atmee
If the portrait and the voice sample are at public https links, skip to the next step.
Otherwise, upload each one first. Start an upload, PUT the file to the uploadUrl it returns,
with its uploadHeaders:
curl -X POST https://api.atmanity.us/v1/uploads \
-H "X-Api-Key: $ATMEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "kind": "portrait" }'
curl -X PUT "<uploadUrl>" -H "Content-Type: image/jpeg" --data-binary @portrait.jpgkind is portrait, voice or knowledge. Poll
GET /v1/uploads/{uploadId} until status is uploaded,
then use upload:<uploadId> wherever the manifest takes a link. An upload works once and expires
an hour after you start it; a create that fails leaves it usable for a retry.
Send the manifest
curl -X POST https://api.atmanity.us/v1/avatars \
-H "X-Api-Key: $ATMEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"schemaVersion": 1,
"name": "Val",
"engine": "v2",
"language": "en",
"visibility": "private",
"previewMessage": "Hi! I am Val. What can I help you find today?",
"assets": {
"image": { "url": "https://cdn.example.com/val/portrait.jpg" },
"voice": { "url": "upload:6f1c2d34-8b7a-4e19-9c5d-0a2b3c4d5e6f" }
}
}'{
"avatarId": "56cc8fdd-ba78-4d48-a210-16cf80c3f5f5",
"status": "building",
"statusUrl": "/v1/avatars/56cc8fdd-ba78-4d48-a210-16cf80c3f5f5"
}A 202 means the build has started. Keep the avatarId. Every field is in
Manifest fields.
Wait for the build
Poll the avatar every few seconds until it settles:
curl https://api.atmanity.us/v1/avatars/56cc8fdd-ba78-4d48-a210-16cf80c3f5f5 \
-H "X-Api-Key: $ATMEE_API_KEY"An Atmee V1 build usually finishes in under a minute; an Atmee V2 build takes a few minutes, longer
when the renderer is starting cold. What each status means is in Build status.
Pick a take (Atmee V2)
An Atmee V2 avatar is captured as one or more takes: short talking clips of it saying the
preview message. By default the first take to finish goes live. To compare, list them and watch
each previewUrl:
curl https://api.atmanity.us/v1/avatars/56cc8fdd-ba78-4d48-a210-16cf80c3f5f5/candidates \
-H "X-Api-Key: $ATMEE_API_KEY"Capture more, or pick another, at any time. See Takes and versions.
Start a conversation
Once readyToChat is true, start a session with the avatarId, as in the
Quickstart.
Create with a zip
Put the manifest and the files in one zip, and send it in one request.
Lay out the zip
my-avatar.zip
├── avatar.json # the manifest (required)
├── image/portrait.jpg # exactly one image
├── voice/sample.wav # exactly one sample; leave out for a render-only avatar
├── prompt/prompt.txt # optional: what the avatar does with the visitor, as text
└── knowledge/ # optional: up to 20 files (pdf, docx, doc, txt, md, html, csv), 50 MB eachWrite avatar.json
The manifest takes the same fields as the JSON create, without assets, plus
knowledge.descriptions for the files in knowledge/:
{
"schemaVersion": 1,
"name": "Grand Valley Concierge",
"engine": "v1",
"language": "en",
"visibility": "private",
"persona": {
"preferredAlias": "Val",
"whoPrompt": "A warm, endlessly patient concierge who knows the resort inside out.",
"greeting": "Hi, I'm Val. How can I help?",
"guardrails": ["Never quote prices that aren't in the knowledge base"]
},
"knowledge": { "descriptions": { "pricing.pdf": "2026 rate card" } }
}prompt/prompt.txt and persona.whatPrompt say the same thing, so give only one of them.
Upload it
curl -X POST https://api.atmanity.us/v1/avatars \
-H "X-Api-Key: $ATMEE_API_KEY" \
-F "file=@my-avatar.zip"Or send the zip itself as the body, with Content-Type: application/zip. A 202 with
status: building and the avatarId means the build has started.
Wait for the build
Poll GET /v1/avatars/{avatarId} as in the JSON path until it settles. See
Build status. For an Atmee V2 avatar, pick a take the same
way.
Render-only avatars. A zip without a voice, or a multipart request with only an image (file)
and a name, makes a render-only avatar: 201, ready at once, for use with your own voice agent
in Avatar mode. Add a voice later with
PUT /v1/avatars/{avatarId}/voice to make it talk
on its own.
Manifest fields
| Field | Notes |
|---|---|
schemaVersion | Required. 1. |
name | Required. 1 to 100 characters, the name in Studio. |
engine | v2 or v1. Without it the API builds v1, so always send it. |
language | The avatar's language: en, zh-TW, zh-CN, ja, ko, fr, vi, es, de, ru, or hi (accepted, spoken in English for now). |
visibility | private (default) or unlisted. Make it public with PATCH once it is ready. |
assets.image.url, assets.voice.url | JSON only. The portrait and the voice sample: a public https link or upload:<uploadId>. |
assets.prompt.url, assets.knowledge | JSON only. The prompt text, and the knowledge files as a list of { "url", "description" }. |
previewMessage | The line the previews speak. On Atmee V2 every take speaks it, up to 60 characters (400 preview_message_too_long). Leave it out for a default sentence in the avatar's language. |
seedPreviews.count | Atmee V2. How many takes to capture now, 1 to 10. Default 1. |
seedSelection | Atmee V2. auto (default): the first finished take goes live. manual: the avatar waits as awaiting_selection until you pick one. |
resolution | Atmee V2. The frame size, square 512 × 512 by default; portrait or landscape where your account offers it. |
persona.whoPrompt | Who the avatar is. Up to 10,000 characters. |
persona.whatPrompt | What it does with the visitor. Up to 10,000 characters. In a zip, this or prompt/prompt.txt. |
persona.greeting | Its opening line, up to 500 characters. |
persona.selfIntroduction | How it introduces itself, 51 to 2,000 characters. Written from whoPrompt when left out. |
persona.guardrails | Up to 20 rules, 500 characters each, used word for word. |
persona.preferredAlias, spokenName, knownAliases | The name it goes by, how to say it, and other names people use. |
persona.conversationalHabits | Studio's style sliders, each CONVERSATIONAL_HABIT_SCALE_LOW, _MEDIUM or _HIGH. |
knowledge.facts | What it knows, as text, up to 32,000 characters. Used by Atmee V1 avatars; Atmee V2 conversations don't use knowledge yet. |
knowledge.descriptions | Zip only. A short description for each file in knowledge/. |
previews | true to also render idle and talking preview clips, off by default. |
personality and scenario still work as older names of whoPrompt and whatPrompt; a field given
under both names is 400 invalid_manifest.
Start with only a name. The portrait and the voice are optional on both paths. An avatar
created without them is a draft (status: draft, with missingInputs naming what it lacks).
Add them with PUT /appearance and
PUT /voice, in either order, and the build
carries on. An Atmee V2 draft captures one take, picked automatically.
Build status
status | Means |
|---|---|
building | Still working. Keep polling. |
ready | Start a session once readyToChat is true. |
awaiting_selection | Atmee V2 with seedSelection: manual: pick a take. |
draft | Created without its portrait or voice; missingInputs says which. |
failed | latestFailure.userFeedback says why, in words you can show your user. |
moderation_hold | Held for review. Visitors can't open it, but your account can still talk to it while readyToChat is true. Don't retry. |
stalled | No progress for a long time; stall.reason says what to replace. Waiting longer won't help. |
The answer also has stages: appearance, voice, persona, knowledge and previews, each
moving through waiting, pending, processing and completed, skipped when it doesn't
apply (such as previews when you didn't ask for them), or failed with a
userFeedback message. A clearly bad portrait fails appearance with a portrait_* reason
(portrait_no_face, portrait_face_too_small, portrait_mouth_closed); replace the portrait to
fix it.
If a create fails on the network, list your avatars before you send it again: a repeated create makes a second avatar.
Takes and versions
Atmee V2 only. Lip sync and motion vary from take to take, and the inputs don't tell you which will be best, so watch them.
- List with
GET /v1/avatars/{avatarId}/candidates. Each item has astatusand apreviewUrl(an mp4, valid about an hour), andservingmarks the take the avatar talks with. - Capture more with
POST /v1/avatars/{avatarId}/candidatesand{ "count": 3 }. New takes come from the current portrait, voice and preview message, and never change the serving take. An account can have at most 10 captures queued or running (429 rate_limitedbeyond that). - Pick one with
PUT /v1/avatars/{avatarId}/selected_candidateand itscandidateId. Only a completed take can be picked; it serves a few seconds later.
A version is one set of capture inputs: the portrait, the voice sample and the preview message.
Changing any of them starts a new version with new takes, and the old take serves until a new one
goes live. Picking a take of an older version goes back to that version's inputs, with nothing
captured again. DELETE /v1/avatars/{avatarId}/versions/{versionId}
removes an older version; the current one, or one holding the serving take, can't be deleted
(version_in_use).
Update it afterwards
- The character and the text fields:
PATCH /v1/avatars/{avatarId}with only the fields to change, such aspersona,knowledge.facts,name,language,visibilityorpreviewMessage. Send an empty value ("",[],{}) to clear one. Every persona edit is moderated again. - The portrait or the voice:
PUT /appearanceorPUT /voice. Only the stages that depend on it run again. On Atmee V2 this starts a new version. - The knowledge files:
PUT /knowledgereplaces the whole set. - An agenda (Atmee V2):
PUT /conductor. - Delete it:
DELETE /v1/avatars/{avatarId}.
No portrait or voice yet?
Generate them. Each call needs a key with generation:write and avatars:write, and an
Idempotency-Key header with a new UUID, so a retry never charges twice.
| Call | Send | Get back |
|---|---|---|
POST /v1/generation/portraits | A description of an invented character | Up to four portraits |
POST /v1/generation/enhancements | Your photo as image, and an optional prompt to restyle it | Up to four portraits that keep the person |
POST /v1/generation/voices | A voiceDescription, and optionally language, avatarName and sampleText | Voice previews, each with the text it speaks |
Each output comes back as upload:<uploadId>: pass it straight to assets.image.url,
assets.voice.url, or a /appearance or /voice replace. You're charged only when at least one
output comes back. Never generate a real person's likeness from a description; for a real person,
enhance their own photo, with their permission.
Errors
A create that can't start answers 400 with an error and a message naming the problem:
error | Means |
|---|---|
invalid_zip, invalid_manifest | The zip or the manifest is malformed. |
invalid_url | A link is not https, carries credentials, or points at a private host. |
fetch_failed | A link couldn't be downloaded. |
upload_not_found, upload_kind_mismatch, invalid_upload | An upload: value is unknown, of the wrong kind, or not uploaded yet. |
preview_message_too_long | An Atmee V2 previewMessage over 60 characters. |
public_visibility_not_allowed_on_create | Create it private or unlisted, then make it public. |
The full list is in the API reference.
Next steps
Quickstart
From an API key to a live avatar on your own page: one server file and one HTML file, with no packages to install.
Generation
Make what an avatar or a video needs: portraits from a description, an enhanced or restyled photo, a designed voice, or a short video clip. Paid per call, charged only when something comes back.

