---
title: "Pin one designed candidate voice to the cast canon."
method: POST
path: "/story-projects/{projectId}/cast-voice/choose"
tags: ["Projects"]
---

# Pin one designed candidate voice to the cast canon.

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

## Request

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

Keeps candidate `candidate` (1-based) of design `designId`: clones its take into the provider that speaks every designed voice (Cartesia), adds it to your voice library as `voiceName` (default "<project> - <character>") with where it came from (the description, the take's preview id, the new voice), pins it on the cast canon person (so every new episode, deck, trailer and showcase speaks with it), and lists in `reads` the finished reads that still speak this character with another voice — re-voice them with POST /artifacts/{artifactId}/revoice (priced). Free; choosing another candidate of the same design later is free too, and choosing one already chosen reuses its voice. A candidate drawn before the designer changed cannot be chosen (409 `cast_voice_precondition_failed`, `details.missing` `take`): design again. A candidate its casting coverage FAILED (GET .../cast-voice/designs/{designId}/coverage, `failingCandidates`) is refused with 409 `cast_voice_casting_coverage_failed` unless `acknowledgeCastingCoverage: true`, sent only once the writer has heard it and chosen it anyway; coverage still running or one that could not run never holds a choice (`castingCoverageWarning` says so).  Requires `Idempotency-Key`. Scope: `story:write`.

## Headers

- `Idempotency-Key` string, required

## Request body

- CastVoiceChooseRequest
  - `designId` string, required
  - `candidate` integer, required: 1-based candidate number from the design.
  - `voiceName` string: Name in your voice library. Default "<project> - <character>".
  - `acknowledgeCastingCoverage` boolean: Choose a candidate its casting coverage FAILED (otherwise refused with 409 `cast_voice_casting_coverage_failed`, `details.castingCoverage` carrying the score, the bar, why and the candidate it recommends). Send true ONLY after the writer has heard it and chosen it anyway — never on your own.

## Response `200`

The voice pinned, and the reads still on another voice.

- CastVoiceChooseResponse
  - `projectId` string, required
  - `designId` string, required
  - `character` string, required
  - `voice` object, required
    - `voiceId` string, required
    - `voiceName` string, required
    - `provider` string, required
    - `gender` string, nullable, required
  - `reads` object[], required: Finished reads that still speak this character with another voice. Re-voice each with POST /artifacts/{artifactId}/revoice.
    - `artifactId` string, required
    - `title` string, nullable, required
    - `voiceId` string, nullable, required
    - `voiceName` string, nullable, required
  - `castingCoverageWarning` string, nullable: What casting coverage said about this choice: still running, could not run, another candidate recommended, or chosen past a failure.
  - `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`: No cast voice design with that id in this project (`voice_design_not_found`), or the project is not yours (`project_not_found`).
- `409`: The candidate cannot be chosen, and nothing was cloned or pinned: `cast_voice_casting_coverage_failed` when its casting coverage FAILED it (`details.castingCoverage`: the score, the bar, why, the candidate it recommends instead and any revised brief; send `acknowledgeCastingCoverage: true` only once the writer has chosen it anyway), or `cast_voice_precondition_failed` for a take drawn before the designer changed.
- `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/86e084144cef?raw)
