---
title: "Create an avatar from a portrait"
method: POST
path: "/v1/avatars"
tags: ["Avatars"]
---

# Create an avatar from a portrait

`POST /v1/avatars`

## Request

- Base URL: `https://realtimeavatar.ai/api`
- URL: `https://realtimeavatar.ai/api/v1/avatars`
- Auth: HTTP bearer

Requires an API key with the `avatars:write` scope.

## Request body

- CreateAvatarRequest
  - `displayName` string, required
  - `sourceKind` 'image' | 'video': `image` is how avatars are created: upload a portrait, and the platform generates the looping source video and the motion library from it. `video` is CLOSED — it survives in this enum for a handful of grandfathered tenants, and `createAvatar` answers everyone else `422` ("Creating from a supplied video is closed"). The gate is a per-tenant flag that fails closed, so treat `video` as unavailable unless you already know yours is on.
  - `modelId` 'realtime-avatar-live-v1' | 'realtime-avatar-video-v1': Omit it. This is not a choice between renderers — the model is DERIVED from `sourceKind` (`image` → `realtime-avatar-live-v1`, `video` → `realtime-avatar-video-v1`), and sending one that disagrees with your `sourceKind` is `422 Avatar model is not compatible with its source kind`. So an explicit value can only ever restate what the source already decided, or fail. It follows that `realtime-avatar-video-v1` is unreachable on the `image` lane everyone uses: it needs `sourceKind: "video"`, which is closed.
  - `sourceAssetId` string
  - `motionPrompt` string
  - `defaultVoiceId` string
  - `voice` union
    - object
      - `auto_description` string, required
      - `voice` object
        - `provider` 'cartesia' | 'fish', required
        - `voice_id` string, required
        - `model` string
        - `speed` number, nullable
        - `emotion` string, nullable
        - `language` string, nullable
    - object
      - `auto_description` string
      - `voice` object, required
        - `provider` 'cartesia' | 'fish', required
        - `voice_id` string, required
        - `model` string
        - `speed` number, nullable
        - `emotion` string, nullable
        - `language` string, nullable
  - `llm` object
    - `provider` 'local' | 'gemini' | 'openai', required
    - `model` string, nullable
  - `settings` object
  - `metadata` object

## Response `201`

Created

- Avatar
  - `id` string, required
  - `tenantId` string, required
  - `displayName` string, required
  - `sourceKind` 'image' | 'video', required
  - `modelId` 'realtime-avatar-live-v1' | 'realtime-avatar-video-v1', required
  - `sourceAssetId` string, nullable, required
  - `status` 'draft' | 'preprocessing' | 'ready' | 'failed' | 'disabled' | 'deleted', required
  - `error` string, nullable, required
  - `idleVideoStatus` 'none' | 'queued' | 'generating' | 'ready' | 'failed', required
  - `defaultVoiceId` string, nullable, required
  - `llm` object, nullable, required
    - `provider` 'local' | 'gemini' | 'openai', required
    - `model` string, nullable
  - `createdAt` string, required
  - `updatedAt` string, required

## Other responses

- `401`: Missing, malformed, revoked, or expired key.
- `403`: The key lacks the scope for this operation, or the tenant is not active.
- `default`: Error. Route-dependent: 400 (a field this route cannot honour — see `portraitUrl` on `UpdateAvatarRequest`), 402 (insufficient credits or spend limit), 404 (no such avatar, voice, or key for this tenant — the message often reads "does not belong to this tenant", which is a missing id and not a permission failure), 408 (the request was cancelled), 409 (the resource is not in a state that accepts this write — a stale `expectedRevision`, a render already in flight, or a mint that asked for a clip library still building), 411 (`POST /v1/assets/remote` only — the origin serving `remoteUrl` sent no `content-length`, so the platform will not stream it), 422 (strict schema rejection, or a motion description the safety screen refused), 429 (rate limited), 502 (upstream render failed), 503 (a dependency this route needs is unavailable — retryable, and nothing was written), 504 (the upstream request budget expired), 500 (unhandled). Switch on `code` where present, else `status`.

---

[API](https://skmtc.dev/realtimeavatar/apis/realtime-avatar-api.md) · [All operations](https://skmtc.dev/realtimeavatar/apis/realtime-avatar-api/llms.txt) · [OpenAPI document](https://skmtc.dev/realtimeavatar/apis/realtime-avatar-api/revisions/e9e3e998e673?raw)
