---
title: "Generate the project cast (price first, draw on a confirmed second call)."
method: POST
path: "/story-projects/{projectId}/cast"
tags: ["Projects"]
---

# Generate the project cast (price first, draw on a confirmed second call).

`POST /story-projects/{projectId}/cast`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/story-projects/{projectId}/cast`
- Auth: HTTP bearer

With scriptUploadId, quote the full episode cast first, then confirm with that quoteId to reserve Studio Credits and enqueue voices plus headshots, body shots and character sheets. Poll GET with the same scriptUploadId; Table Read requires complete casting. Without scriptUploadId this is the separate project Bible visual cast.

## Headers

- `Idempotency-Key` string, required

## Request body

- GenerateProjectCastRequest: Pass scriptUploadId for complete paid episode casting. Omit it for project Bible visual casting.
  - `scriptUploadId` string: Which finalized episode to cast. Omit for project-level Bible visuals.
  - `characters` string[]: Limit to these character names (case-insensitive). Omit to cast everybody castable. Episode casting gives NARRATOR a voice but no images.
  - `force` boolean: Re-draw Face, Body and Sheet even where art already exists (only `images`, when given). This REPLACES pictures the writer may have chosen.
  - `images` string[]: Narrow the cast to these pictures and no voices: `face` (the headshot), `body` (the body shot), `sheet` (the character sheet). With `force` they are drawn again: the Redraw on the project's Cast page, a body shot or sheet drawn from the person's chosen headshot and checked against it. Priced per picture; two calls (the quote's `quoteId` confirms). With scriptUploadId it narrows that episode's cast. WITHOUT scriptUploadId it redraws people on the project cast canon, including people no episode names: `body` and `sheet` only (a headshot is redrawn as candidates, POST /cast-portraits/design; `face` is a 400), the chosen headshot is kept, and each picture is written onto the cast canon (a ProjectCastRedrawQuote, then a ProjectCastRedrawQueued; read the cast until `redraw` is complete). A body shot or sheet for a person with no headshot is refused (409 `cast_precondition_failed`, `details.reason: "cast_face_missing"`).
  - `quoteId` string: Required when confirming episode casting or a picture redraw; returned by its quote.
  - `confirmed` boolean: Omit (or false) to get the price and the plan WITHOUT drawing anything. Pass true only after the writer has seen that plan and said go.

## Response `200`

The quote, durable episode casting job, or completed project Bible visual cast.

- ProjectCastEnvelope
  - `cast` union, required: Branch on `quotedOnly`: true means nothing was drawn and this is the price.
    - ProjectCastQuote: The answer to a call WITHOUT `confirmed`: what would happen, what it costs, what it would leave (`balanceAfter` / `sufficient`), and the fact that it has not happened.
      - `projectId` string, required
      - `scriptUploadId` string, nullable, required: The named episode, or null for project Bible visuals.
      - `quoteId` string: Episode quote identity required on confirmation.
      - `voices` integer
      - `quotedOnly` true, required
      - `credits` integer, required: Studio Credits required for the quoted casting work.
      - `balanceAfter` integer, required: Quote only. Studio Credits the writer would have left once this is spent: the balance available now (as Billing shows it) minus the price. Negative means they are that many short.
      - `sufficient` boolean, required: Quote only. True when the available balance covers the price. False means the writer must top up first (the note names the Billing link) — do not confirm; the reserve would refuse.
      - `costLabel` string, required
      - `characters` string[], required: Everyone in scope for this call.
      - `drawing` string[], required: Who a confirmed call would draw.
      - `alreadyDrawn` string[], required: Who already has a Face and would be left alone.
      - `faces` integer, required
      - `images` integer, required: Every picture the run commissions: each Face, plus the Body and Sheet that follow it.
      - `note` string, required: The same sentence to show the writer, in their terms.
    - ProjectCastResult: The answer to a call WITH `confirmed: true`.
      - `projectId` string, nullable, required
      - `scriptUploadId` string, nullable, required
      - `quotedOnly` false, required
      - `members` ProjectCastMember[], required
        - `character` string, required
        - `face` string, nullable, required: The headshot, or null when it could not be drawn.
        - `reused` boolean, required: True when the Face came from an already-rendered portrait rather than a fresh render.
        - `error` string: Present only when this character has no Face; says why.
        - `artChecks` object[]: The body shot and sheet drawn in this call, each checked against the face: the same person, age, build and wardrobe. A drawing that did not match was drawn once more at our cost (`retaken`) and the closer kept. Never blocks the cast.
          - `slot` 'body' | 'sheet', required
          - `status` 'match' | 'mismatch' | 'unavailable' | 'skipped', required: `mismatch`: neither draw matched (the closer was kept); `unavailable`: could not be checked; `skipped`: no face to compare with.
          - `retaken` boolean, required
          - `scores` object, nullable, required
            - `samePerson` number, required
            - `ageAndBuild` number, required
            - `wardrobe` number, required
          - `mismatches` string[], required
          - `note` string, required
      - `faces` object, required
        - `generated` integer, required
        - `reused` integer, required
        - `failed` integer, required
      - `bodyAndSheet` 'incomplete' | 'complete', required: Whether all required body shots and character sheets finished successfully.
      - `note` string, required
    - EpisodeCastQueuedResult
      - `projectId` string, required
      - `scriptUploadId` string, required
      - `quotedOnly` false, required
      - `jobId` string, required
      - `status` 'queued' | 'running' | 'retrying' | 'complete' | 'failed' | 'cancelled', required
      - `progressDetail` string, nullable
      - `error` string, nullable
      - `credits` integer, required
      - `note` string, required
      - `pictures` CastJobPicture[]: The pictures this job draws.
        - `character` string, required: The cast member's cue.
        - `image` 'face' | 'body' | 'sheet', required
    - ProjectCastRedrawQuote: A project-canon picture redraw (`images` without scriptUploadId), priced and not drawn: confirm with `quoteId`.
      - `projectId` string, required
      - `scriptUploadId` unknown, required
      - `quotedOnly` true, required
      - `quoteId` string, required: Required on the confirmed call. A cast that changed since (a headshot, a picture, the drawing model) refuses it: 409 `cast_quote_changed`.
      - `credits` integer, required: Studio Credits, per picture at the episode cast rate.
      - `balanceAfter` integer, required: Quote only. Studio Credits the writer would have left once this is spent: the balance available now (as Billing shows it) minus the price. Negative means they are that many short.
      - `sufficient` boolean, required: Quote only. True when the available balance covers the price. False means the writer must top up first (the note names the Billing link) — do not confirm; the reserve would refuse.
      - `costLabel` string, required
      - `characters` string[], required: Everyone in scope, by their canon name.
      - `drawing` string[], required
      - `alreadyDrawn` string[], required: Who already has those pictures and would be left alone (without `force`).
      - `faces` 0, required: A redraw never draws a headshot.
      - `images` integer, required
      - `pictures` CastJobPicture[], required
        - `character` string, required: The cast member's cue.
        - `image` 'face' | 'body' | 'sheet', required
      - `note` string, required
    - ProjectCastRedrawQueued: A confirmed project-canon picture redraw: reserved and queued. Read the cast (GET without scriptUploadId) until `redraw.status` is complete.
      - `projectId` string, required
      - `scriptUploadId` unknown, required
      - `quotedOnly` false, required
      - `jobId` string, required: Empty when there was nothing to redraw.
      - `status` 'queued' | 'running' | 'complete' | 'failed', required
      - `progressDetail` string, nullable, required
      - `error` string, nullable, required
      - `credits` integer, required
      - `pictures` CastJobPicture[], required
        - `character` string, required: The cast member's cue.
        - `image` 'face' | 'body' | 'sheet', required
      - `note` string, required

## Other responses

- `400`: Request payload failed validation. The error message names the offending field.
- `401`: Missing, malformed, revoked, or expired API key.
- `403`: API key is valid but lacks the scope, is blocked by IP allowlist, or the account is disabled.
- `404`: The named project or source does not exist or is owned by another account.
- `409`: Casting cannot run on this project yet, and nothing was quoted or drawn: nobody castable, nobody with a description to draw from, no passing screenplay coverage, or no image model bound (`cast_precondition_failed`, `details.reason` names which) — or the project itself has not reached casting (`project_precondition_failed`, `details.stage` names the stage to finish). The message says what to do.
- `429`: Rate limit for this API key + IP + route family was exceeded.
- `500`: The API failed unexpectedly. Safe to retry with the same Idempotency-Key.

---

[API](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api.md) · [All operations](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api/revisions/64c1102ccb2f?raw)
