---
title: "Undo a voice migration."
method: POST
path: "/voice-migrations/{migrationId}/undo"
tags: ["Voices"]
---

# Undo a voice migration.

`POST /voice-migrations/{migrationId}/undo`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/voice-migrations/{migrationId}/undo`
- Auth: HTTP bearer

Every use the migration re-pointed that still holds the replacement goes back to the old voice; a use changed again since, or a read finished on the new voice since, is kept and listed in `migration.undo.kept` with the reason. A migration is undone once. Refused (409 `voice_retired`) once the old voice's provider has retired it, because the undo would pin a voice that can no longer speak. Free. Requires `Idempotency-Key`. Scope: `story:write`.

## Headers

- `Idempotency-Key` string, required

## Response `200`

The undone migration.

- VoiceMigrationUndoResponse
  - `migration` VoiceMigrationRecord, required
    - `migrationId` string, required
    - `createdAt` string, date-time, required
    - `from` VoiceMigrationVoice, required
      - `voiceId` string, required
      - `voiceName` string, nullable, required
      - `provider` string, nullable, required
      - `retiresAt` string, date-time, nullable, required: When the voice's provider retires its voices, if it is retiring.
    - `to` VoiceMigrationVoice, required
      - `voiceId` string, required
      - `voiceName` string, nullable, required
      - `provider` string, nullable, required
      - `retiresAt` string, date-time, nullable, required: When the voice's provider retires its voices, if it is retiring.
    - `scope` object, required
      - `projectId` string, nullable
      - `character` string, nullable
    - `changes` VoiceMigrationChange[], required
      - `kind` 'canon' | 'read' | 'trailer' | 'pitch_deck', required
      - `projectId` string
      - `projectName` string
      - `jobId` string
      - `artifactId` string, nullable
      - `title` string, nullable
      - `character` string
      - `previous` object, required
      - `next` object, required
    - `skipped` VoiceMigrationSkippedRead[], required
      - `jobId` string, required
      - `artifactId` string, nullable, required
      - `title` string, nullable, required
      - `characters` string[], required
      - `reason` string, required
    - `undoneAt` string, date-time, nullable, required
    - `undo` object, nullable, required
      - `at` string, date-time, required
      - `restored` VoiceMigrationChange[], required
        - `kind` 'canon' | 'read' | 'trailer' | 'pitch_deck', required
        - `projectId` string
        - `projectName` string
        - `jobId` string
        - `artifactId` string, nullable
        - `title` string, nullable
        - `character` string
        - `previous` object, required
        - `next` object, required
      - `kept` object[], required
        - `change` VoiceMigrationChange: One re-pointed use: `canon` (a cast canon person: projectId, projectName, character), `read` (a read with no finished recording: jobId, artifactId, title, character) or `trailer` / `pitch_deck` (its pinned narrator: jobId, title). `previous` is what it held; `next` what it holds now.
          - `kind` 'canon' | 'read' | 'trailer' | 'pitch_deck', required
          - `projectId` string
          - `projectName` string
          - `jobId` string
          - `artifactId` string, nullable
          - `title` string, nullable
          - `character` string
          - `previous` object, required
          - `next` object, required
        - `reason` string
  - `note` string, required

## Other responses

- `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 voice with that id in your library, or another writer's cloned or designed voice, which is never yours to preview, cast, pin or migrate onto (`voice_not_found`). GET /voices lists the ones you own; a catalog voice can be previewed and assigned but not renamed or deleted.
- `409`: A voice its provider has retired is in the way (`voice_retired`), so nothing was queued, charged or changed. `details.voices` names each one; migrate them (POST /voice-migrations), then retry.
- `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)
