---
title: "Batch-update the cast (voices + avatars) on a table read artifact."
method: POST
path: "/artifacts/{artifactId}/cast"
tags: ["Artifacts"]
---

# Batch-update the cast (voices + avatars) on a table read artifact.

`POST /artifacts/{artifactId}/cast`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/artifacts/{artifactId}/cast`
- Auth: HTTP bearer

Batch-updates the cast on the table read backing this `table_read` artifact in one call, in place. Each `entries` item targets one character and may reassign its voice (`voiceId` / `voiceName` / `gender` / `provider`), request an avatar re-render (`regenerateAvatar: true`, with an optional `avatarRefinement` art-direction nudge), or both. A top-level `avatarStyle` restyles ALL portraits (re-renders every character against the new art-direction). Provide at least one `entries` item OR a top-level `avatarStyle` — a body with neither is rejected as `validation_failed`. Voice reassignments apply immediately. Avatar work is async + queue-backed: the response returns the updated `voiceMap` plus one avatar op `{ character, status, chatToolJobId }` per queued render — poll GET /artifacts/{artifactId}/cast until each character's `avatarStatus` reaches `ready`. No new revision — the artifact id and share URLs stay stable, like a voice recast (no charge).  Requires `Idempotency-Key`. Scope: `artifact:publish`.

## Headers

- `Idempotency-Key` string, required

## Request body

- union: Batch cast update. Provide at least one `entries` item OR a top-level `avatarStyle` — a body with neither is rejected as `validation_failed`. A top-level `avatarStyle` is an episode-only override that re-renders ALL portraits without changing project Bible canon.
  - object
    - `entries` CastEntryUpdate[], required: Per-character updates. Optional when a top-level `avatarStyle` is supplied.
      - `character` string, required: Character cue to update.
      - `voiceId` string: New voice id to assign.
      - `voiceName` string: Human-readable voice name for display.
      - `gender` string: Optional voice gender hint. Preserved from the prior assignment when omitted.
      - `provider` 'cartesia' | 'elevenlabs' | 'openai' | 'deepgram' | 'hume': Optional TTS provider. Inferred when omitted.
      - `avatarRefinement` string: Optional per-character art-direction nudge for this character's avatar render.
      - `regenerateAvatar` boolean: Set true to queue an avatar re-render for this character.
      - `avatarUrl` string, uri: Directly set this character's portrait to existing hosted art (a series' canonical character sheet); no generative render. Must be an https URL.
    - `avatarStyle` string: Optional episode portrait override applied across the whole cast (at most 8000 characters: it is saved as the episode `avatar` override and takes that override's cap). Re-renders every character without changing project Bible canon.
    - `confirmed` boolean: PRICED when the batch draws portraits (a restyle, `regenerateAvatar` or `avatarRefinement`; 3 credits a character). Omit to get the price of the whole batch only (`quotedOnly: true`; nothing, voice changes included, is applied); send `true` to apply it and charge. A batch that draws no portrait is free and needs no confirmation.
  - object
    - `entries` CastEntryUpdate[]: Per-character updates. Optional when a top-level `avatarStyle` is supplied.
      - `character` string, required: Character cue to update.
      - `voiceId` string: New voice id to assign.
      - `voiceName` string: Human-readable voice name for display.
      - `gender` string: Optional voice gender hint. Preserved from the prior assignment when omitted.
      - `provider` 'cartesia' | 'elevenlabs' | 'openai' | 'deepgram' | 'hume': Optional TTS provider. Inferred when omitted.
      - `avatarRefinement` string: Optional per-character art-direction nudge for this character's avatar render.
      - `regenerateAvatar` boolean: Set true to queue an avatar re-render for this character.
      - `avatarUrl` string, uri: Directly set this character's portrait to existing hosted art (a series' canonical character sheet); no generative render. Must be an https URL.
    - `avatarStyle` string, required: Optional episode portrait override applied across the whole cast (at most 8000 characters: it is saved as the episode `avatar` override and takes that override's cap). Re-renders every character without changing project Bible canon.
    - `confirmed` boolean: PRICED when the batch draws portraits (a restyle, `regenerateAvatar` or `avatarRefinement`; 3 credits a character). Omit to get the price of the whole batch only (`quotedOnly: true`; nothing, voice changes included, is applied); send `true` to apply it and charge. A batch that draws no portrait is free and needs no confirmation.

## Response `200`

A batch that draws portraits, sent without `confirmed`: the price of the whole batch, nothing applied. Otherwise: cast updated, returning the merged voiceMap plus the in-flight avatar ops to poll and the credits charged.

- union
  - CastArtQuote: The answer to a priced cast-art call WITHOUT `confirmed`: what it costs, what it would leave, and the fact that nothing was drawn or charged. The same price on every surface (the app's Cast panel, this API, MCP, the CLI and Slug).
    - `artifactId` string
    - `quotedOnly` true, required
    - `credits` integer, required
    - `balanceAfter` integer: 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: 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, nullable
    - `kind` 'portrait' | 'figure', required
    - `characters` string[], required
    - `note` string, required: The same sentence to show the writer, in their terms.
  - CastBatchResponse: Result of a batch cast update. Voice reassignments are already applied in `voiceMap`; each item in `avatars` is a queued avatar render to poll, charged in `credits` (0 when the batch drew no portrait).
    - `artifactId` string, required
    - `quotedOnly` false, required
    - `credits` integer, required: Studio Credits held for the portraits the batch draws.
    - `voiceMap` object, required: Full updated cast: character → { voiceId, voiceName, gender, provider }.
    - `avatars` AvatarOp[], required: One op per queued avatar render.
      - `character` string, required
      - `status` 'queued' | 'rendering' | 'ready' | 'failed', required: Lifecycle of the avatar render op.
      - `chatToolJobId` string, required: Id of the queued `regenerate_avatar` job backing this render.

## 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 story artifact does not exist or is owned by another account.
- `409`: The artifact is not in a state where this operation is valid yet (e.g. requesting an MP4 render before the share link exists).
- `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/4534f5d25540?raw)
