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

# Views and lifecycle

> The eight named views, how a view slot moves between states, which operation each character status allows, and why a photo is refused.

A character is a set of **eight named views**. You can let the platform render every one of them ([`POST /v1/characters`](/api-reference/characters/create-character)), or assemble the set yourself with the builder — [create a draft](/api-reference/characters/create-draft), [file your own photo into a view](/api-reference/characters/put-ref), then [build](/api-reference/characters/build-character) whatever is still missing.

This page is the shared vocabulary those routes assume: the views, the slot states, the statuses each operation accepts, and the reasons a photo is refused. Every endpoint page links back here rather than repeating it.

## The eight views

| Part       | View          | What the photo shows                                                     |
| ---------- | ------------- | ------------------------------------------------------------------------ |
| Head       | `head_front`  | Head and shoulders, facing the camera — the face every generation copies |
| Upper body | `upper_front` | Chest to hips, facing the camera                                         |
| Lower body | `lower_front` | Hips through legs, facing the camera                                     |
| Upper body | `upper_back`  | Rear upper body, head to waist                                           |
| Lower body | `lower_back`  | Rear lower body, waist through legs                                      |
| Full body  | `full_left`   | Standing, head to feet, strict left profile                              |
| Full body  | `full_front`  | Standing, head to feet, facing the camera                                |
| Full body  | `full_right`  | Standing, head to feet, strict right profile                             |

That is canonical order — the order `views[]` comes back in, the order `refs[]` is sorted in, and the `order` field on [`GET /v1/characters/views`](/api-reference/characters/list-views). Fetch the catalogue rather than hardcoding the list; it also carries the `constraints` to pre-check a photo against locally.

### `view` and the legacy `pose` label

`refs[].pose` predates named views and is **locked at five values** for compatibility. `refs[].view` is the real view name. Four of the eight views project onto `other`, so `pose` can never round-trip back to a view — read `view` (or `views[]`) in new code.

| View          | `refs[].pose` |
| ------------- | ------------- |
| `head_front`  | `portrait`    |
| `full_front`  | `front`       |
| `full_left`   | `side`        |
| `upper_back`  | `back`        |
| `upper_front` | `other`       |
| `lower_front` | `other`       |
| `lower_back`  | `other`       |
| `full_right`  | `other`       |

## The `views[]` read model

Every character read carries `views[]` with **exactly eight entries, always** — a view the character does not have is present with `status: "missing"`, never absent. Each entry carries `view`, `status`, `url`, `nudity`, `reason`, `detected_view` and `made_from`.

| `status`    | Meaning                                                                                                                                                               |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uploaded`  | The slot holds a photo you supplied. It is used as-is and is never re-rendered by a build.                                                                            |
| `generated` | The slot holds a platform render. `made_from` names the views it was conditioned on.                                                                                  |
| `rejected`  | Your most recent photo for this slot was refused and no accepted reference took its place. `reason` says why; `detected_view` is set when the reason is `wrong_view`. |
| `missing`   | The slot is empty. It appears in `build.missing_views` and a build renders it.                                                                                        |

The set is **open** — treat an unrecognized value as `missing`.

### How a slot moves

```
missing ──PUT accepted──▸ uploaded ──PUT accepted──▸ uploaded   (replace)
   │
   ├──PUT refused──▸ rejected ──PUT accepted──▸ uploaded
   │                     │
   │                     └──24 h with no further attempt──▸ missing
   │
   └──build / regenerate──▸ generated
```

* A refusal stores nothing: the slot keeps whatever it already held, and a slot that held nothing becomes `rejected` rather than `uploaded`.
* `rejected` is a **memory of your last attempt**, not a stored reference. It is surfaced only while the character is a `draft` (a built character has no pending refusals to annotate) and it expires **24 hours** after the attempt, at which point the slot reads `missing` again.
* A `generated` slot can be overwritten too: an accepted `PUT` turns it `uploaded`, and from then on [regenerate](/api-reference/characters/regenerate-view) refuses it with `400 reference_uploaded`.
* [`DELETE /v1/characters/{id}/refs/{view}`](/api-reference/characters/delete-ref) sends a slot back to `missing`, and is accepted only while the character is a `draft`.

## Which operation each status allows

| Operation                                                                                      | Allowed `status`                        | Anything else                                                                                          |
| ---------------------------------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| [`PUT /v1/characters/{id}/refs/{view}`](/api-reference/characters/put-ref)                     | `draft`, `failed`, `reviewing`, `ready` | `synthesizing` → `409 character_busy`; others → `400 character_status_invalid`                         |
| [`DELETE /v1/characters/{id}/refs/{view}`](/api-reference/characters/delete-ref)               | `draft`                                 | `synthesizing` → `409 character_busy`; others → `400 character_status_invalid` (`param: character_id`) |
| [`POST /v1/characters/{id}/build`](/api-reference/characters/build-character)                  | `draft`, `failed`                       | `synthesizing` → `409 character_busy`; others → `400 character_status_invalid` (`param: character_id`) |
| [`POST /v1/characters/{id}/refs/{view}/regenerate`](/api-reference/characters/regenerate-view) | `reviewing`, `ready`                    | `synthesizing` → `409 character_busy`; others → `400 character_status_invalid`                         |
| [`PATCH /v1/characters/{id}`](/api-reference/characters/update-character) changing `nudity`    | `draft`                                 | `400 character_status_invalid` (`param: nudity`)                                                       |

<Note>
  After a build fails, a view can be **replaced** (`PUT`) but not **removed** (`DELETE` requires `draft`) until the next successful build. Nothing was lost: the references you uploaded are stored on the character, not as expiring upload tickets, so [`POST /v1/characters/{id}/build`](/api-reference/characters/build-character) from `failed` is the recovery path and needs no re-upload.
</Note>

## Nudity

`nudity` (`nude` or `clothed`) is a property of the **whole reference set**. Set it on [`POST /v1/characters`](/api-reference/characters/create-character) or [`POST /v1/characters/drafts`](/api-reference/characters/create-draft), change it with `PATCH` while the character is still a `draft`, and after that it is fixed. Omit it and the character takes your team's default. On teams that always render clothed, an explicit `nude` returns [`400 nudity_not_allowed`](/errors#nudity_not_allowed).

Rules for a photo you file into a view:

* A photo showing nudity is **refused** on a `clothed` character — `422 reference_unfit`, `reason: "nudity_mismatch"` — **except on `head_front`, which carries no nudity rule**.
* A clothed body photo on a `nude` character is **accepted**. It is not a failure: the slot comes back as `views[].nudity: "clothed"` while the character's own `nudity` stays `nude`. Compare the two to detect a mixed set.
* Every *generated* view reports the character's own `nudity`, so a mixed set can only come from photos you uploaded.
* `head_front` carries no nudity rule, so its `views[].nudity` describes **that photo** and is not a mixed-set signal: a head-and-shoulders shot normally reports `clothed` even on a `nude` character. Read the mixed-set signal off the body views.

## Why a photo is refused

A refusal is `422 reference_unfit` with a machine-readable `reason` (and `detected_view` when the reason is `wrong_view`). Branch on `reason`, never on the prose `message`. Nothing is stored, nothing is charged, and **the upload ticket stays usable** — re-send the same photo to a different view without re-uploading the bytes.

| `reason`          | `param`     | When                                                                                                                                                                                                                                                                                                                                                            |
| ----------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `too_small`       | `upload_id` | The short side is under `constraints.min_short_side_px`.                                                                                                                                                                                                                                                                                                        |
| `extreme_aspect`  | `upload_id` | Longest ÷ shortest side is over `constraints.max_aspect_ratio`.                                                                                                                                                                                                                                                                                                 |
| `no_person`       | `upload_id` | No person was found in the photo.                                                                                                                                                                                                                                                                                                                               |
| `multiple_people` | `upload_id` | More than one person is in frame — crop to the model alone.                                                                                                                                                                                                                                                                                                     |
| `no_face`         | `upload_id` | `head_front` only: no clear, unobstructed face.                                                                                                                                                                                                                                                                                                                 |
| `wrong_view`      | `view`      | The photo shows a different view than the one you addressed. `detected_view` names what it looks like — offer that slot instead of asking for a new photo. A `detected_view` of `other` means the photo matches none of the eight views (sitting, lying down, a close-up of something other than the face) — there is no slot to offer, so ask for a new photo. |
| `nudity_mismatch` | `upload_id` | The character's set is `clothed` and the photo shows nudity — **except on `head_front`, which carries no nudity rule**.                                                                                                                                                                                                                                         |

Those seven are the **complete** `reason` set on a 422.

<Note>
  `views[].reason` is a **superset** of the table above. A photo can also be refused before the fit rules ever run — for a format the platform does not accept, or for dimensions outside the hard ceiling — and those two refusals are error *codes* rather than fit reasons: they come back as `400 invalid_format` or `400 value_out_of_range` with **no** `reason` on the envelope, while the slot has to remember something. So `views[].reason` is the seven reasons above **plus** `invalid_format` and `value_out_of_range` — nine values in total. Keep a default branch either way.
</Note>

### 400 or 422?

* **`400`** — the request is wrong in a way you can see from your own side: an unknown `{view}`, a missing or malformed `upload_id`, a character whose `status` does not allow the operation, a format the platform does not accept.
* **`422`** — the request is well-formed and the platform had to *look at the bytes* to decide: dimensions, and what the photo actually depicts. You cannot predict a `422` from the request alone, which is why it is its own status.

A failed visual check is neither: it is [`503 reference_check_unavailable`](/errors#reference_check_unavailable) with a `Retry-After` in **seconds**. Retry — nothing was stored and nothing was charged.

## Version note

<Note>
  These changes ship on the existing `2026-08-26` contract. No `Aurous-Version` pin isolates them: the version catalogue carries image and video pricing pointers only, not character pricing or the size of the reference set. Pinning an earlier `Aurous-Version` restores neither the smaller reference set nor the previous price. A character created before 2026-09-14 keeps the references it already has — add either of the two newer views on demand with [`POST /v1/characters/{id}/refs/{view}/regenerate`](/api-reference/characters/regenerate-view); every character created on or after that date carries eight.
</Note>

## Common pitfalls

* `build.missing_views` and `views[]` are derived from the references that exist **right now**. Re-read the character after every `PUT` rather than caching a plan.
* A `draft` cannot be used for generation. `POST /v1/images` or `POST /v1/videos` with a draft's `character_id` returns `400 character_not_ready`.
* A `rejected` slot is not an error state you have to clear. Upload a fitting photo, remove the slot, or leave it — a build renders whatever is `missing` or `rejected` all the same.
