# HeyGen Photo Avatar 4 (lipsync) > HeyGen Photo Avatar 4 animates a photo with a clear face to an audio track and controls the output frame directly: a resolution preset, an aspect ratio and a talking style. Status: roasOS API v1.1. Catalog `model-catalog@2026-10-06.v5`, pricing `customer-pricing@2026-11-05.v15`. The endpoint `POST /v1/lipsync/heygen-avatar-4` is live. `POST /v1/generations` stays for one minor version as a deprecated alias (see "Today"). ## Endpoint POST https://api.roasos.com/v1/lipsync/heygen-avatar-4 Auth: Authorization: Bearer roasos_sk_… (scope api:write). Idempotency-Key required. quote_only / quote_id / webhook_url as in https://docs.roasos.com/llms.txt. ## Good at Presenter-style ads where the delivery format matters more than identity nuance: one photo can produce a 9:16 cut and a 16:9 cut. `talking_style` selects minimal movement (`stable`) or more animation (`expressive`). Give it a front-facing, well-lit photo. It does not accept a prompt or a video source, and its built-in text-to-speech (`prompt` + `voice`) is not exposed. ## Pricing Unit: second. Pricing key: `heygen-avatar-4`. | request | credits | |---|---| | `10s` | 12 | Credits come from the roasOS pricing table (`MODEL_COSTS`) through the catalog. No other field changes the price. ## Modes - `image-audio-to-video` Provider endpoints: `fal-ai/heygen/avatar4/image-to-video`. ## Request fields | field | type | required | default | rule | |---|---|---|---|---| | `aspect_ratio` | enum | no | `16:9` | `16:9`, `9:16`, `4:5`, `5:4`, `1:1`, `auto` | | `audio_url` | string | yes | — | https URL; ≤ 4,096 characters | | `avatar_id` | string | no | — | UUID | | `image_url` | string | no | — | https URL; ≤ 4,096 characters | | `media_rights` | object | yes | — | `{ accepted, statement_version }` | | `quote_id` | string | no | — | UUID | | `quote_only` | boolean | no | `false` | — | | `resolution` | enum | no | `720p` | `360p`, `480p`, `540p`, `720p`, `1080p` | | `talking_style` | enum | no | `stable` | `stable`, `expressive` | | `wait_timeout_seconds` | integer | no | `0` | 0–60 | | `webhook_url` | string | no | — | https URL; ≤ 4,096 characters | The schema is strict: a field that is not in this table returns `400 unsupported_option`. Field names are the roasOS role names, one name per role across every family. ## Media - `avatar_id` — identity; the saved character reference sheet, with a derived portrait only where the model requires a single face or source frame - `image_url` — first frame (or the single source image) - `audio_url` — the driving audio track | role | items | max size | content types | duration | |---|---|---|---|---| | `audio_url` | ≤ 1 | 15 MB | `audio/mp3`, `audio/mpeg`, `audio/wav`, `audio/wave`, `audio/x-wav` | 0–60 s | | `image_url` | ≤ 1 | 10 MB | `image/jpeg`, `image/png`, `image/webp` | — | - Refused: `elements`, `product_id`, `product_images`, `references`, `reference_images`, `end_image_url`, `scene_id`, `scene_image`, `video_urls`, `video_url`, `audio_urls`, `voice_id`. A refused role returns `400 unsupported_option`. - Every URL must be an immutable object on `https://cdn.roasos.com/…` that your workspace owns; any other URL is `400 reference_media_not_workspace_owned`. Upload with `POST /v1/uploads` (direct POST of the bytes to `put_url`, or a `source_url` import), then use the returned CDN URL. ## Prompt None. The endpoint has no prompt. (At fal, `prompt` is the text the avatar speaks; roasOS requires `audio_url` instead.) ## Not exposed - `prompt`: not accepted by this public endpoint. - `voice`: not accepted by this public endpoint. - `expression`: not accepted by this public endpoint. - `background`: not accepted by this public endpoint. - `caption`: pinned to `false`. ## Result Result kind `lipsync`. Always present: `url`. ```json { "data": { "id": "gen_…", "kind": "lipsync", "model": "heygen-avatar-4", "status": "completed", "result": {"url":"https://cdn.roasos.com/workspaces/ws_01/result"} } } ``` Files live on `https://cdn.roasos.com` and are immutable; a result URL can be a media input to the next call. Poll `GET /v1/jobs/{id}/status` → `{ id, status, updated_at }`, then `GET /v1/jobs/{id}/result` (`409 run_not_completed` until the job is `completed`, `failed` or `canceled`). Statuses: `queued → processing → completed | failed | canceled`. ## Example ```bash # 1. quote — nothing is spent curl -X POST https://api.roasos.com/v1/lipsync/heygen-avatar-4 \ -H "Authorization: Bearer roasos_sk_live_…" \ -H "Idempotency-Key: 0f1c9e2a-heygen-avatar-4-0001" \ -H "Content-Type: application/json" \ -d '{"quote_only":true,"audio_url":"https://cdn.roasos.com/workspaces/ws_01/uploads/audio_url.mp3","image_url":"https://cdn.roasos.com/workspaces/ws_01/uploads/image_url.png","media_rights":{"accepted":true,"statement_version":1}}' # → 200 { "data": { "kind": "quote", "quote_id": "q_7Hk…", "credits": 12, "expires_at": "…", "confirmation": { "model": "heygen-avatar-4", "example": "10s" } } } # 2. run — the same body and the same Idempotency-Key, quote_id instead of quote_only curl -X POST https://api.roasos.com/v1/lipsync/heygen-avatar-4 \ -H "Authorization: Bearer roasos_sk_live_…" \ -H "Idempotency-Key: 0f1c9e2a-heygen-avatar-4-0001" \ -H "Content-Type: application/json" \ -d '{"quote_id":"q_7Hk…","audio_url":"https://cdn.roasos.com/workspaces/ws_01/uploads/audio_url.mp3","image_url":"https://cdn.roasos.com/workspaces/ws_01/uploads/image_url.png","media_rights":{"accepted":true,"statement_version":1}}' # → 201 { "data": { "id": "gen_…", "kind": "lipsync", "model": "heygen-avatar-4", "status": "queued", "credits_reserved": 12, "links": { "status": "/v1/jobs/gen_…/status", "result": "/v1/jobs/gen_…/result" } } } # 3. poll curl https://api.roasos.com/v1/jobs/gen_…/status -H "Authorization: Bearer roasos_sk_live_…" curl https://api.roasos.com/v1/jobs/gen_…/result -H "Authorization: Bearer roasos_sk_live_…" ``` ## Today Not available on the public API. The contract of `POST /v1/lipsync/heygen-avatar-4` is frozen and the route answers 501 `model_endpoint_not_implemented`: the model needs a recorded media-rights attestation and a server-measured audio duration, and the public API captures neither yet. `POST /v1/generations` returns 503 `lipsync_public_authority_unavailable` for the same reason. The app route `POST /api/generate/lipsync` with `model: "heygen-avatar-4"`, `rights_accepted: true` and camelCase options (`talkingStyle`, `aspectRatio`) reaches the provider. ## See also https://docs.roasos.com/models/lipsync/heygen-avatar-4 · https://api.roasos.com/v1/catalog/lipsync/heygen-avatar-4 · fal-ai/heygen/avatar4/image-to-video