> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Video avatar agent and render errors

> Decode and fix the VIDEO_AVATAR_AGENT_* and VIDEO_AVATAR_RENDER_* refusals on the avatar persona registry and the async avatar-video render surface — cause by code, the one fix that clears it, and the render state machine.

# Video avatar agent and render errors

The avatar persona registry (`/api/v1/video/avatar-agents`) and the async
render-job surface (`/api/v1/video/avatar-video-renders`) return a small,
deterministic family of 4xx codes: `VIDEO_AVATAR_AGENT_NOT_FOUND`,
`VIDEO_AVATAR_AGENT_INVALID`, `VIDEO_AVATAR_RENDER_NOT_FOUND`,
`VIDEO_AVATAR_RENDER_INVALID`, and
`VIDEO_AVATAR_RENDER_INVALID_TRANSITION`. None of them is transient and
none clears on retry — each names a payload field or a job state you
control. This page maps every code to its cause and the one fix that
clears it.

## What video avatar agents are

An avatar persona binds one of your conversational agents (the dialog
brain) to a rendered talking-head provider reference, then either joins a
live video room as a digital host or renders personalized clips from a
script template with `{{merge_tag}}` tokens. This runbook covers the error
surface only; the feature itself, end to end, is documented in
[AI video avatar agents](/guides/ai-video-avatar-agents).

## Where these codes surface

Both surfaces are tenant-scoped and authenticated (`video:read` for reads;
`video:write` plus an owner, admin, or developer role for writes), and both
answer failures with the same error envelope: an `error` object carrying
`code`, `message`, and `status`, and a `meta` object carrying `request_id`
and `timestamp`:

```json theme={null}
{
  "error": {
    "code": "VIDEO_AVATAR_RENDER_NOT_FOUND",
    "message": "Render job not found.",
    "status": 404
  },
  "meta": {
    "request_id": "req_01J9ZK0Q3VX8M2N4P5R6S7T8W9",
    "timestamp": "2026-10-16T09:12:44.201Z"
  }
}
```

* **The persona registry** — `GET` and `POST /video/avatar-agents`,
  `PATCH` and `DELETE /video/avatar-agents/:id`, and
  `GET /video/avatar-agents/:id/join-plan`. Persona ids are
  `vaa_`-prefixed. A persona exists only for the tenant whose API key you
  called with.
* **The render-job surface** — `GET` and `POST
  /video/avatar-video-renders`, `GET`, `PATCH`, and `DELETE
  /video/avatar-video-renders/:id`, plus `POST
  /video/avatar-video-renders/verify`. Render job ids are
  `vavr_`-prefixed. `POST` dereferences the persona the job binds, so a
  render request can return codes from either family.

When validation rejects a payload, `message` names the failing field —
read it before you change anything.

## Code-by-code decoder

| Code | Status | Cause | Fix |
| - | - | - | - |
| `VIDEO_AVATAR_AGENT_NOT_FOUND` | 404 | The `vaa_` id is not in the calling tenant's registry: an id copied from another tenant, a typo, or a persona deleted between your read and your write. On `POST /video/avatar-video-renders` it also fires when `avatar_agent_id` names a persona you never registered. | List the registry for the calling tenant with `GET /video/avatar-agents` and look the id up. If the persona should exist, register it with `POST /video/avatar-agents` before re-issuing the dependent call. |
| `VIDEO_AVATAR_AGENT_INVALID` | 400 | One field of the persona payload fails validation: empty or over-80-char `name`; an `agent_id` that is not a valid `agt_` id; a `provider` outside `tavus`, `heygen`, `did`; a missing or over-200-char `provider_persona_ref`; a `greeting` over 500 chars; a `locale` over 35 chars; a rename colliding with an existing persona name; the 50-persona cap; or, on join-plan, a missing or over-256-char `room`. | Read `error.message` — it names the rejected field and its bound. Fix that one field and re-send once. |
| `VIDEO_AVATAR_RENDER_NOT_FOUND` | 404 | The `vavr_` job id does not exist for the calling tenant: a typo, the wrong tenant, or a job already removed with `DELETE /video/avatar-video-renders/:id`. | Confirm with `GET /video/avatar-video-renders/:id`, or list with `GET /video/avatar-video-renders` filtered by `?status=` or `?persona_id=`. If the job is gone, queue a fresh one with `POST`. |
| `VIDEO_AVATAR_RENDER_INVALID` | 400 | On `POST`: the bound persona is disabled; `script_template` is empty or over 5000 chars; `merge_fields` breaks a bound (20 keys max, keys of 1-64 letters/digits/underscores, values of 500 chars max); the template references a `{{token}}` with no supplied value; or `locale` exceeds 35 chars. On `PATCH` reporting `completed`: the clip payload is missing (either `video_base64`, or both `video_sha256` and `video_bytes`), `video_base64` decoded to zero bytes, or `output_url` is absent. | Fix the named field. Supply a value for every `{{token}}` the template references — unresolved tokens are rejected up front, never rendered blank. A `completed` report must carry `output_url` plus one of the two clip-digest forms. |
| `VIDEO_AVATAR_RENDER_INVALID_TRANSITION` | 409 | A `PATCH` status report the state machine forbids — most often a report against a job already in a terminal state (`completed` or `failed`), or a transition that skips ahead of the job's current state. | Read the job's current `status` with `GET /video/avatar-video-renders/:id`, then follow the state machine below. Terminal jobs are closed; queue a fresh render instead of updating one. |

Two adjacent codes on the same surfaces are worth knowing:
`VIDEO_AVATAR_AGENT_DISABLED` (409) when a join plan targets a persona
stored with `"enabled": false` — re-enable it with
`PATCH /video/avatar-agents/:id` and dispatch again — and the `*_FAILED`
5xx family (`VIDEO_AVATAR_AGENT_CREATE_FAILED`,
`VIDEO_AVATAR_RENDER_UPDATE_FAILED`, …), which are transient storage-side
faults and, unlike the five codes above, do take one backoff retry.

## The render state machine

A render job moves `queued → rendering → completed` or
`queued → rendering → failed`; `completed` and `failed` are terminal.
What each verb accepts per state:

| Job state | `GET` | `PATCH` (status report) | `DELETE` |
| - | - | - | - |
| `queued` | yes | yes — to `rendering`, `completed`, or `failed` | yes |
| `rendering` | yes | yes — to `completed` or `failed` | yes |
| `completed` / `failed` | yes | no — 409 `VIDEO_AVATAR_RENDER_INVALID_TRANSITION` | yes |

`POST /video/avatar-video-renders` always creates a new job; it never
resumes an old one. A `completed` report that lacks `output_url` or the
clip digest is refused as 400 `VIDEO_AVATAR_RENDER_INVALID`, not 409 — the
state was legal, the payload was not.

## Worked examples

### 404 — rendering against a persona the tenant never registered

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/video/avatar-video-renders" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "avatar_agent_id": "vaa_9f8e7d6c5b4a39281706f5e4d3c2b1a0",
    "script_template": "Hi {{first_name}}, here is your update."
  }'
```

```json theme={null}
{
  "error": {
    "code": "VIDEO_AVATAR_AGENT_NOT_FOUND",
    "message": "Avatar persona not found.",
    "status": 404
  },
  "meta": {
    "request_id": "req_01J9ZKT2M4N6P8Q0R2S4T6U8V0",
    "timestamp": "2026-10-16T09:31:02.884Z"
  }
}
```

Register the persona first (`POST /video/avatar-agents`), or point
`avatar_agent_id` at an id from `GET /video/avatar-agents`, then post the
render again. Re-posting the render without that step returns the same 404
every time — a render never creates a persona.

### 409 — reporting status against a terminal job

```bash theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/video/avatar-video-renders/vavr_7d93c2b1a0f9e8d7c6b5a4938271605f" \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "status": "rendering" }'
```

```json theme={null}
{
  "error": {
    "code": "VIDEO_AVATAR_RENDER_INVALID_TRANSITION",
    "message": "This render job already reached a terminal status (completed) and cannot be updated.",
    "status": 409
  },
  "meta": {
    "request_id": "req_01J9ZKV7W9X1Y3Z5A7B9C1D3E5",
    "timestamp": "2026-10-16T09:44:17.530Z"
  }
}
```

The job already finished. If two reporters race on one job, keep a single
reporter per job id; if you need another clip, queue a fresh render with
`POST`.

## Retrieve and reconcile before you retry

A 404 or 409 means the world moved under your request. Before re-issuing
any write:

1. `GET /video/avatar-agents` — confirm the persona id exists for the
   tenant whose key you are sending.
2. `GET /video/avatar-video-renders/:id` — read the job's current `status`
   and compare it with the transition you were about to report.
3. Re-issue the write only once the read shows the corrected state — the
   persona listed, or the job in a state that accepts your transition.

Never blind-retry a 404: the registry and the job list are
read-your-writes, so a retry against a missing id fails identically until
the object exists.

## What not to do

* Do not re-`POST` a render that 404'd with
  `VIDEO_AVATAR_AGENT_NOT_FOUND` — create or fix the persona first; the
  render endpoint never creates one.
* Do not `PATCH` a `completed` or `failed` job to resume it — terminal is
  terminal; queue a fresh render.
* Do not loop a 400 — the refusal is deterministic and names the field;
  the payload will not fix itself between attempts.
* Do not treat these five codes as an outage — they never indicate a
  platform fault. The `*_FAILED` 5xx family does; retry those once with
  backoff.

## Escalate

If a code disagrees with the state your reads show — a 404 on a persona
that `GET /video/avatar-agents` lists, or a 409 on a job whose `status` is
`queued` — open a support ticket carrying:

* `meta.request_id` from the failing response,
* the persona id (`vaa_…`) and/or the render job id (`vavr_…`),
* the verb and path you called, and the response status.

Related: [AI video avatar agents](/guides/ai-video-avatar-agents) for the
end-to-end workflow,
[Avatar video render lifecycle](/concepts/avatar-video-render-lifecycle)
for the job model and its content credential, and the
[Error code reference](/reference/error-codes) for codes without a
dedicated runbook.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.