---
title: "Recast a single character's voice on a table read artifact."
method: POST
path: "/artifacts/{artifactId}/voice"
tags: ["Artifacts"]
---

# Recast a single character's voice on a table read artifact.

`POST /artifacts/{artifactId}/voice`

## Request

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

Reassigns one character's voice on the table read backing this artifact, in place. The artifact id, share token, and all player URLs remain STABLE — no new revision, no charge.  Body: `{ character, voiceId, voiceName, gender?, provider?, pinToCanon? }`. `provider` is the TTS provider for the voice (e.g. `cartesia`, `openai`); omit to infer it (your library voice's own, else the read's, else the configured table-read provider). A voice whose provider retired it is refused (409 `voice_retired`). THE CAST CANON FOLLOWS: renders (decks, trailers, showcases) prefer the canon voice, so the recast also pins it on the canon person the character names (by name or alias) unless `pinToCanon: false`. `canon` names the person pinned. NOTHING IS LOST: when the voice actually changes on a read that has a recording, the previous voice, the voices-only track and the finished mix are kept before the recording is invalidated, and POST /artifacts/{artifactId}/voice/restore puts them back (`restore` describes what it would restore). A recast to the voice the character already has changes nothing (`changed: false`). To keep every other line and re-voice only this character in a finished read, use POST /artifacts/{artifactId}/revoice instead. Returns the updated `voiceMap` (character → voice). The current cast is also readable on `manifest.audio.voiceMap` via GET /artifacts/{id}.  Requires `Idempotency-Key`. Scope: `artifact:publish`.

## Headers

- `Idempotency-Key` string, required

## Request body

- object
  - `character` string, required: Character name as it appears in the cast (e.g. "NARRATOR").
  - `voiceId` string, required: Provider voice id to assign.
  - `voiceName` string, required: Human-readable voice name (shown in the cast).
  - `gender` string: Optional voice gender hint. Preserves the existing value when omitted.
  - `provider` 'cartesia' | 'elevenlabs' | 'openai' | 'deepgram' | 'hume': Optional TTS provider for this voice. Inferred when omitted (your library voice's own, else the read's, else the configured table-read provider).
  - `pinToCanon` boolean: Also pin this voice on the cast canon person (default true), so decks, trailers and showcases speak with it too. false changes this read only.

## Response `200`

Voice recast. Returns the updated cast as `voiceMap`.

- object
  - `artifactId` string, required
  - `character` string, required
  - `voiceMap` object, required: Full updated cast: character → { voiceId, voiceName, gender, provider }.
  - `changed` boolean, required: false when the character already had this voice: nothing was invalidated or remembered.
  - `canon` object, nullable, required: The cast canon person now pinned to this voice; null when the canon was not touched (`pinToCanon: false`, or a script in no project).
    - `projectId` string, required
    - `person` string, required
  - `restore` RestorableVoice, required: A recast that POST /artifacts/{artifactId}/voice/restore can undo.
    - `character` string, required
    - `historyId` string, required
    - `at` string, date-time, required
    - `previousVoiceId` string, nullable, required: The voice a restore puts back; null when the character had none.
    - `previousVoiceName` string, nullable, required
    - `currentVoiceName` string, nullable, required
    - `recordingRecoverable` boolean, required: A restore also brings back the recording the read had before the recast.

## 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)
