# Studio for agents

Studio is the part of Missiver (https://missiver.com/studio) where a founder and their agents make images, video and audio, keep them in projects, and build reusable cast. This page lists what an agent can do in Studio **today**, with the first steps of each task, and points to the full contract for each. The protocol for everything else is https://missiver.com/agent.md.

Only call the endpoints listed here or in the pages they link to. If the founder asks for something this page marks as not available yet, tell them so plainly; do not invent a route.

## 1. Connect

1. Find the company key: `.env.missiver` (`MISSIVER_TOKEN=ceod_...`) in the company's repo, or `.env.ceo-dashboard` (`CEO_DASHBOARD_TOKEN=`) in a repo set up before the rename. Load it for one command at a time and never print it (agent.md, [Authentication](https://missiver.com/agent.md#authentication)).
2. Check media/project access: `GET https://missiver.com/api/cli/media/assets?action=projects` with `Authorization: Bearer <key>`.
   - `200 {projects:[...]}`: connected, with media/project access.
   - `401`: no key, or a revoked one. Ask the founder to create a key at https://missiver.com/connect.
   - `403 media_permission_required`: the key works but Studio access is off. Ask the founder to turn on **Connect CLI → Studio access for agents** for this key.
3. No key in the repo? Ask the person to approve a connection for you in their browser ([Connect without a key](https://missiver.com/agent.md#connect)); the key it delivers goes in the repo's `.env.missiver`. The founder can also make a key on the Connect page.

This probe checks the media/project rail. Its Studio switch does not gate contribution terms, a private actor draft's own-plan/owned-upload path, or an authenticated full-access agent's explicit revise/retry under the draft's saved permissions and caps. Follow each endpoint's error code; a `403` does not always mean founder-only.

## 2. Recipes

<!-- Studio series W5 -->
<a id="series"></a>

### Fill out a Series identity

A project of kind `series` (or `movie`) carries one **Series identity** that every generation in it inherits: idea, logline, Film setup (genre, era and tempo, each with an optional one-line reason), color scheme, look, format, style lock, cast and wardrobe. The person sees each change on the series page as made by you, with Undo. The examples use a series called Backstage Hours.

1. Find it by name: `GET /api/cli/media/assets?action=projects&kind=series&q=Backstage%20Hours`. Matching ignores case, punctuation and spacing, and `&` equals `and`; each candidate carries `match: {normalized, exact}`. One exact match: use it and say which. Several: list them and ask. None: offer `?action=project-create` with `{"name":"Backstage Hours","kind":"series"}` (or `"kind":"movie"`) and create it only when the person says yes.
2. Read it: `GET /api/cli/media/assets?action=series-identity&id=<project-id>` returns `identity`, `identity_stamp` (`version`, `at`), your `role`, `enums`, `filled` (what is already set), `cast` and `history_count`. `enums` is the authority for the values below.
3. Write once with what you read: `POST /api/cli/media/assets?action=series-identity&id=<project-id>` with an `Idempotency-Key` and `{"set": {...}, "expected": {"version": 1, "at": "<identity_stamp.at>"}}`. Send only what changes. Settable: `name`, `oneLine`, `idea`, `film` (`genre`, `era`, `tempo`, `genreReason`, `eraReason`, `tempoReason`), `rendition`, `palette`, `art` (`lock`), `formats`, `wardrobe`.
   - `genre`: general, epic, drama, noir, comedy, horror, action. `era`: auto, 2020s, 2000s, 1990s, 1980s, 1960s. `tempo` (video only): auto, single_shot, chaotic, dynamic, calm. `rendition`: anime-2d, cinematic.
   - Each reason is one plain line of at most 140 characters, for people and agents, and is never sent to a model. An empty string clears it.
   - `palette` maps group names to `#rrggbb` colors or `{"hex","name"}` objects, `main` first; `formats[0].aspect` is the series format, such as `9:16`.
4. `422 invalid_field` names `field`, `value` and `allowed`: fix that one field and send again. `409 conflict` returns the current `identity` and `identity_stamp`: merge your change into it and send once more with the new stamp.
5. `GET ?action=series-identity-history&id=<project-id>` lists each change with who made it (`by.agent` is your key's name). To undo, `POST ?action=series-identity-undo&id=<project-id>` with `{"history_ids": [...]}`: all or nothing; `409 undo_conflict` names the fields changed since.

### Add cast to a series

1. Make or find each character first (the [private-actor draft recipe](https://missiver.com/agent.md#make-an-actor-for-the-founder), [Cast import](https://missiver.com/studio-cast-import.md), or a published house actor). Each has a lowercase handle such as `mara-quill`.
2. The owner's agent attaches them: `POST /api/cli/media/assets?action=series-cast&id=<project-id>` with `{"handles": ["noa", "kai", "miso"], "operation": "add"}`, 1 to 12 handles a call. A series holds as many characters as its story needs; call again for more. Each result is `attached` or `handle_not_found`. Add `"placeholders": true` to hold a handle that has no sheet yet (`no_sheet_yet`; it attaches itself once the sheet exists). `"operation": "remove"` takes handles off.
3. Wardrobe per attached character goes through `series-identity`: `{"set": {"wardrobe": {"mara-quill": ["Long cream fur coat", "Chain boots"]}}, "expected": ...}`.

### Members, generating and results in a series

- The owner shares: `POST ?action=project-member-add&id=<project-id>` with `{"email": "...", "role": "editor"}` or `"viewer"` (`404 no_account` when no Missiver account uses that address); `POST ?action=project-member-update&id=<project-id>` with `{"user_id": "...", "role": "viewer"}` or `{"user_id": "...", "remove": true}`; `GET ?action=project-members&id=<project-id>`. A member's agent has exactly that member's role; owner-only calls answer `403 owner_only`.
- Compile first, free: `POST /api/cli/media/compile` with your usual quote body plus `"destinationProjectId": "<project-id>", "identity": "inherit", "cast": ["mara-quill"]`, an optional `"shot"` (close-up, medium, full, back) and optional `"overrides": {"film": {"genre": "comedy"}}` or `{"rendition": "cinematic"}` for this one shot, each listed as a deviation. The reply lists every line the model receives, where it is sent, and what is not sent.
- Quote the same body at `POST /api/cli/media/quotes` and accept it as usual. Whoever generates pays from their own company, and the quote names the payer. Viewers cannot generate (`403 viewer_cannot_generate`).
- `GET ?action=series-generations&id=<project-id>` (paged with `cursor`) lists results with who made each and the identity stamp it used; `POST ?action=series-generation-download&id=<project-id>&asset=<asset-id>` returns a short-lived download link to the owner or the person who made it.

Series errors: `409 identity_changed` (the identity changed after your quote: compile and quote again), `403 series_access_revoked` (your membership ended; nothing was charged), `503 series_not_ready` (series are not available in this workspace yet), `422 prompt_too_long` (the compiled prompt passed the model's limit), `400 series_required` (identity, cast or overrides sent without a series destination).
<!-- /Studio series W5 -->

<!-- Micro-drama S2: episodes -->
<a id="episodes"></a>

### Make an episode of a series

A series holds numbered **episodes**. Each episode is a clock (a fixed length, or a song's title and length) and a list of frames: `id`, start and end in seconds (`a`, `b`), the cast handles in the shot, the sung and spoken lines, a note, the still, and a **check** that compares the still with each character's plate. The person sees every episode on the series page's Episodes tab, with what you changed and when. All verbs are on `/api/cli/media/assets` with `&id=<series project id>`; read with any role, write as owner or editor (a viewer gets `403 series_forbidden`).

1. **Find the series** as in [Fill out a Series identity](#series): `GET /api/cli/media/assets?action=projects&kind=series&q=<name>`.
2. **List and read**: `GET ?action=episodes&id=<series>` lists every episode with its counts (`status.frames`, `checked`, `draft`, `held`, `queued`, `described`) and `revision`. `GET ?action=episode&id=<series>&episode=<number>` returns the record with short-lived `previews` and the series' check `thresholds`.
3. **Import what exists** once per episode: read the run-through on the person's machine and post `?action=episode-import` (recipe: [Import a run-through](/studio-episode-import.md)). Never send us a site to fetch; the `source.url` you send is stored as text only.
4. **Start a new episode**: `POST ?action=episode-create` with `{"number", "title", "clock", "frames": [...]}` that you boarded on the clock, or `"script"` and let the plain boarding lay it out. Send `"dryRun": true` first to see the proposal (frames, the speakers the cast knows and does not know) without writing anything. A speaker who is not in the series cast is `422 speaker_not_in_cast`; add them to the cast, or send `"allowUnknownSpeakers": true` to keep them as text. The episode number taken is `409 episode_exists`.
5. **Edit**: `POST ?action=episode` with `{"episode", "expected": {"revision"}, "set": {...}, "frames": {"upsert": [...], "remove": [...]}}` for titles, the clock, retimes, inserts and notes. A frame's still and check never change here; they come from the next two verbs.
6. **Draw each frame** on the person's own plan or model first. Compile for free with `POST /api/cli/media/compile` (`"identity": "inherit"`, the frame's `cast` and `shot`, the note as the prompt), and list the routes in cost order with `GET /api/cli/studio/compare?task=image.redraw&count=<frames>&size=<series format>`. Use Studio only when the person chose it, quoted: `POST /api/cli/media/quotes`, then accept and poll `/api/cli/media/jobs`.
7. **Attach and check**: `POST ?action=episode-frames` with up to 60 frames `{"id", "assetId", "measure": "agent", "method": "<your model>", "lines": [...]}` when you can see the still and the plates, else `"measure": "house"` (a free house check, capped at 300 a day per series; over the cap the frame stays pending and says so; `503 house_checks_off` when house checks are switched off and you sent no lines). Upload a still first with `?action=upload` and `?action=complete`. Your lines per character are `face:<handle>` (0 to 1) and `wardrobe:<handle>` (`{"seen", "missing"}`), plus `extra-faces` (a count) and, with two or more characters, `blend`. Never send `status`, `ok`, `at`, `jobId`, `attempts` or `history`: the server derives them from the series' thresholds and measures palette drift itself. A character with no sheet yet has no plate to compare against: do not score their face. A `face:<handle>` line for them is dropped, the frame shows as described (not referenced), and the reply carries `"warnings": ["face_without_plate:<handle>"]`.
8. **Fix once, then ask**: a held frame (`check.status: "fail"`) lists the failing lines in words. `POST ?action=episode-fix` with `{"fix": "redraw"}` returns a ready `request` for the compile and quote; run it on the cheapest route the person allows and attach again. `"draft"` keeps the frame as a draft; `"recheck"` queues a new house check. After two failures the server answers `409 needs_choice` with the attempts: tell the person what failed twice and the fixes in cost order, and wait for their choice.
9. **Thresholds**: `GET ?action=series-checks` reads them; only the owner changes them (`POST`, `{"set": {...}}`, kept in History with Undo). To ask whether one still would pass outside an episode: `POST ?action=series-check` with `{"assetId", "cast": [...], "measure": "agent", "method", "lines"}` (or `"measure": "house"`).
10. **Mark posted**: `POST ?action=episode` with `{"set": {"flags": {"posted": "<YYYY-MM-DD>"}}}`.

**Every write** takes an `Idempotency-Key` (8 to 120 letters, numbers or `._:-`) and `expected.revision`. A stale revision is `409 revision_conflict` with the current record: merge into it and send once more. A retry with the same key and body returns the first reply. When that first reply was larger than 16 KB (about 25 to 35 frames), the retry returns the same record while the episode is still at that revision; after another write it answers `409 replay_unavailable` with the current `revision`: read the episode before deciding, and never resend blindly. Keys are kept for 24 hours. Writes share the 30 a minute write allowance; `episode-frames` and `series-check` also share 30 a minute per series, and `episode-import` 10 a minute per series (`429 rate_limited` with `retry_after`). Without the episodes schema the verbs answer `503 episodes_not_ready`.

Everything here is free except the drawing, which runs on the person's own accounts first and on Studio only when quoted and accepted.
<!-- /Micro-drama S2: episodes -->

### Make an image, video or voiceover

1. Build the ranked list first: the founder's own subscriptions and connectors, free local tools, then Studio ([Subscription first](https://missiver.com/agent.md#subscription-first-pay-with-what-the-founder-already-has)).
2. `GET /api/cli/media/models` is the only authority on which models exist and their ratios, qualities, durations and prompt limits.
3. Quote: `POST /api/cli/media/quotes` (add `destinationProjectId` to save into a project). Tell the founder the credits, the dollars and their balance (`GET /api/cli/credits`).
4. Accept with `{quote_id, max_credits}` and an `Idempotency-Key`; poll the job; download the result. Full contract: [Studio workflows, direct jobs](https://missiver.com/studio-workflows.md#direct-image-video-and-voiceover-jobs).

### Run a connected workflow

`GET /api/cli/workflows/templates` to discover, `POST /api/cli/workflows/quotes` to price, then run, review each stage and download. A retry does not approve a workflow review stage. The [private-actor draft policy](https://missiver.com/agent.md#make-an-actor-for-the-founder) separately permits an authenticated full-access agent's explicit revise/retry after a two-failure stop, with visible attribution and unchanged caps. Contract: [Studio workflows](https://missiver.com/studio-workflows.md).

### Bring in work made elsewhere (0 credits)

Upload finished media with `POST /api/cli/media/assets?action=upload`, or hand a result into a workflow stage instead of paying Studio to make it. See [Bring your own outputs](https://missiver.com/agent.md#bring-your-own-outputs-0-credits).

## 3. What exists today

| Task | Route | Contract |
|---|---|---|
| List, create, rename, archive projects | `/api/cli/media/assets?action=projects`, `project-create`, `project-update` | [studio-projects.md](https://missiver.com/studio-projects.md) |
| Add or remove assets in a project | `/api/cli/media/assets?action=project-membership` | [studio-projects.md](https://missiver.com/studio-projects.md#api-access) |
| Browse and search assets | `/api/cli/media/assets?action=list&collections=1` | [studio-projects.md](https://missiver.com/studio-projects.md#browse-and-reuse) |
| Upload media | `/api/cli/media/assets?action=upload` | [studio-workflows.md](https://missiver.com/studio-workflows.md) |
| Models, quotes, direct jobs | `/api/cli/media/models`, `/api/cli/media/quotes`, `/api/cli/media/jobs` | [studio-workflows.md](https://missiver.com/studio-workflows.md#direct-image-video-and-voiceover-jobs) |
| Connected workflows | `/api/cli/workflows/*` | [studio-workflows.md](https://missiver.com/studio-workflows.md) |
| Cast import from photos and video | `/api/cli/continuity/imports` | [studio-cast-import.md](https://missiver.com/studio-cast-import.md) |
| Characters, locations, reviewed shots | `/api/cli/continuity/*` | [studio-continuity.md](https://missiver.com/studio-continuity.md) |
| Wardrobe and actor library | `/api/cli/library/*` | [studio-library.md](https://missiver.com/studio-library.md) |
| Sounds | `/api/cli/sounds/*` | [studio-sounds.md](https://missiver.com/studio-sounds.md) |
| Read a Relay Cut series (backed up) | `/api/cli/series`, `/api/cli/series/<id>` | [studio-series-backup.md](https://missiver.com/studio-series-backup.md#read-a-series-characters-cast-cloud) |
| Credit balance and prices | `/api/cli/credits` | [agent.md](https://missiver.com/agent.md#credits-and-prices) |

## 4. Series release

Movies and series, the Series identity, series cast, sharing a series as Editor or Viewer, and connecting an agent without a key are available now: the recipes are under [Series](#series) above, the record and roles in [Studio projects](https://missiver.com/studio-projects.md#movies-and-series), and the keyless connection in [agent.md](https://missiver.com/agent.md#connect).

## Errors you will meet

- `401`: the key is missing, malformed or revoked. Ask for a new one; do not retry.
- `403 media_permission_required`: media/project access is off for this key. Other `403` codes identify the endpoint's broker, saved permission, or founder-only refusal; read that code's contract (deleting an asset is founder-only today).
- `402 insufficient_credits`: tell the founder their balance and the price; do not switch to another paid option.
- `409`: a conflict or a stale value. Read again, then retry once with what you read.
- `429`: too many requests. Wait and retry later.
