---
title: "Update a table read's recording with the lines changed since it was made (free; TWO CALLS, quote first)."
method: POST
path: "/artifacts/{artifactId}/update-recording"
tags: ["Artifacts"]
---

# Update a table read's recording with the lines changed since it was made (free; TWO CALLS, quote first).

`POST /artifacts/{artifactId}/update-recording`

## Request

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

Brings the read's finished recording up to the read as it is now. The read knows what each line of its recording was spoken from — its voice, its words and its delivery (emotion, speed, volume, acting direction) — and the pause after it. A line whose words, voice or delivery changed since (a line edit, refine_performance) is spoken again by its voice's provider; every other line is kept byte for byte; a line whose only change is the pause after it is not spoken at all — the track is stitched again with the new pause; a line the read no longer speaks is stitched out. Then the read is mixed again with its music and sound, and the old recording keeps playing until the new one lands. FREE: 0 credits however many lines change — nothing is held, charged or booked, and `credits` is always 0. `mode` says what it would do: `respeak`, `restitch` (nothing spoken), `remix` (only the music or sound moved), `record` (no recording yet) or `current` (nothing to update). A recording with lines said over each other was mixed into one track, so no line can be cut from it (`spliceable: false`): every line is made again. TWO CALLS: sent without `confirmed` it QUOTES and changes nothing (`quotedOnly: true`, `credits` 0, `mode`, `changedLines`, `respokenLines`, `pauseChanges`, `removedLines`, `keptLines`, the changed `lines`, `balanceAfter`, `sufficient`, and a `note` to show the writer); sent again with `confirmed: true` it does it. Poll GET /artifacts/{artifactId} until `manifest.audio.finalize.status` is `complete`. Refused before the quote with 409 `cast_voice_precondition_failed` (`details.reason`): `finalize_running` (the recording is being made right now), `no_recording` (a read with no table-read artifact yet). 409 `voice_retired` when a line it must speak again is in a voice its provider retired.  Requires `Idempotency-Key`. Scope: `artifact:publish`.

## Headers

- `Idempotency-Key` string, required

## Request body

- UpdateRecordingRequest
  - `confirmed` boolean: Omit (or false) to get the quote (free: 0 credits) without changing anything. true does it, only after the writer has seen the quote and said go.

## Response `200`

The quote, 0 credits (no `confirmed`), or the queued update.

- UpdateRecordingEnvelope
  - `quotedOnly` boolean, required: true when this call only QUOTED it.
  - `artifactId` string, required
  - `mode` 'respeak' | 'restitch' | 'remix' | 'record' | 'current', required: All free. `respeak`: changed lines spoken again, the rest kept. `restitch`: only pauses moved or lines were taken out, nothing spoken. `remix`: only the music or sound moved. `record`: no recording yet. `current`: nothing to update.
  - `mix` 'current' | 'stale' | 'unknown' | 'null', nullable, required: Whether the finished recording carries the read as it is now (its lines and its mix). Null: no finished recording yet.
  - `changedLines` integer, required: Lines whose words, voice or delivery (speed, volume, acting direction and what it was sent as) changed since the recording, or that are new.
  - `untrackedLines` integer, required: Lines recorded before the read kept track of line changes: spoken again too.
  - `respokenLines` integer, required: Lines spoken again in all.
  - `pauseChanges` integer, required: Lines whose only change is the pause after them: the track is stitched again, nothing is spoken.
  - `removedLines` integer, required: Lines the read no longer speaks: stitched out.
  - `keptLines` integer, required: Lines kept from the recording byte for byte.
  - `spliceable` boolean, required: false when the recording has lines said over each other (mixed into one track), so no line can be cut from it and every line is made again.
  - `lines` object[], required: The lines that change, in read order (the first 25).
    - `entryIndex` integer, required
    - `character` string, required: Who says it: the character cue, or NARRATOR.
    - `text` string, required: Its words (up to 120 characters).
    - `change` 'respeak' | 'pause' | 'removed', required
  - `credits` integer, required: Always 0: updating the recording is free.
  - `costLabel` string, nullable: Quote only.
  - `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.
  - `note` string, required: What it would do, and that it is free (quote), or what happens next (confirmed). Show it to the writer.
  - `status` 'queued' | 'complete': Confirmed calls only. `queued`: poll GET /artifacts/{artifactId} until `manifest.audio.finalize.status` is complete. `complete`: the recording already was the read.

## 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 read or project is not ready for this cast voice operation, and nothing was priced or charged (`cast_voice_precondition_failed`): no description and no Series Bible voice line, a character with no lines, nothing to restore, no finished recording to re-voice, a voice the character already speaks with, a finalize already running, or a recording that cannot be cut line by line. The message says which and what to do; `project_precondition_failed` when the project gate holds spending.
- `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)
