---
title: "List the artifacts produced by a story job."
method: GET
path: "/story-jobs/{jobId}/artifacts"
tags: ["Artifacts"]
---

# List the artifacts produced by a story job.

`GET /story-jobs/{jobId}/artifacts`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/story-jobs/{jobId}/artifacts`
- Auth: HTTP bearer

## Response `200`

Artifact list.

- StoryArtifactListResponse
  - `artifacts` StoryArtifact[], required
    - `id` string, required
    - `storyJobId` string, required
    - `type` 'pitch_deck' | 'table_read' | 'trailer' | 'production_video', required: `production_video` is a dialogue video made before storyboarding was retired (2026-10-09). Those artifacts are still read and published; no new ones are made.
    - `status` 'PENDING' | 'RUNNING' | 'READY' | 'FAILED', required: Lifecycle of an individual artifact within a job. `READY` means the manifest URLs are populated and the artifact is consumable.
    - `manifest` union, required
      - TableReadArtifactManifest: Manifest for a `table_read` artifact. The share token authorizes the returned URLs (not the customer API key), so the consumer can embed or hand them to end-users. The artifact id, share token, and all player URLs remain STABLE across refine operations. The MP4 `video` output and the durable MP3 `audio.finalize` are opt-in.
        - `type` 'table_read'
        - `status` 'ready'
        - `shareToken` string: Expiring JWT share token. Public but unguessable ("unlisted"). Stable across refines.
        - `theaterUrl` string, uri: Framed visual theater-mode page (`/share/table-read/{token}`) with attribution chrome. Stable across refines.
        - `theaterFullscreenUrl` string, uri: Immersive, chrome-free fullscreen theater (same in-app theater stage). Ideal for embeds/kiosk. Stable across refines.
        - `script` object: About the delivered screenplay.
          - `lengthNote` object, nullable: Why the delivered screenplay sits outside the approved page band, when it does (an estimate from word count, not layout). Null on a clean run.
          - `punchUp` object, nullable: What the punch-up pass did to the screenplay, when the request set `punchUp: true`. Null when it did not.
            - `scenes` integer, required: Scenes the punch-up pass looked at.
            - `kept` integer, required: Scenes that kept their punch-up.
            - `reverted` object[], required: Scenes that kept their written version, with why (a verbatim quote dropped, a neverSay term introduced, the slugline changed, a speaker added, grown past the limit, or the call failed). A scene that ran long and missed again on its one retry lists both, the second prefixed "after one retry:".
              - …
            - `retried` integer: Scenes that only ran long and were asked once to cut back to their length; `kept` counts any that then passed.
            - `skippedReason` string: Set when the pass did not run at all; the written screenplay was performed.
            - `modelId` string
        - `audio` object: Audio output section. `liveUrl` opens the audio player (serves the durable MP3 once available). The durable MP3 is auto-rendered on ready — UNLESS the artifact request set `deferAudioRender`, or this is a season-run episode, in which case it is rendered only when you POST /artifacts/{id}/finalize.
          - `mode` 'durable_recording' | 'live_on_demand': `durable_recording` — auto-rendered MP3; `audio.recordingUrl` is populated once complete. (`live_on_demand` is a legacy value for older artifacts.)
          - `liveUrl` string, uri: Minimal-chrome, audio-only player page (`/share/table-read-audio/{token}`). Serves the durable auto-rendered MP3; shows a "Preparing…" state until the render is complete.
          - `recordingUrl` string, uri, nullable: Populated after a successful audio finalize (auto-render on ready, or explicit POST /artifacts/{id}/finalize). `null` until finalize is complete — and stays `null` indefinitely when `deferAudioRender` was set and you have not finalized yet.
          - `recordingDurationMs` integer, nullable: Duration of the finalized MP3 recording in milliseconds. Populated alongside `recordingUrl` on finalize completion.
          - `voiceMap` object: Current cast: character name → { voiceId, voiceName, gender, provider }. Recast a single character in place with POST /artifacts/{id}/voice (scope `artifact:publish`).
          - `modifications` ArtifactVoiceModification[]: Voice modification records applied to entry ranges on this read — one per POST /artifacts/{id}/voice-modification, newest last per range. This is where a dispatched modification is polled to completion, and the ONLY place a render failure explains itself.
            - `id` string, required: Matches the `modificationId` returned when the modification was dispatched.
            - `type` 'autotune' | 'performance-booth', required: Effect applied, or `performance-booth` for a speech-to-speech override.
            - `startEntryIndex` integer, required
            - `endEntryIndex` integer, required
            - `status` 'queued' | 'rendering' | 'ready' | 'failed', required: Lifecycle of this record. `failed` is terminal for this record; re-dispatch the range to retry.
            - `error` string, nullable: Human-readable reason the render failed when `status` is `failed` — including upstream TTS provider errors such as an exhausted credit balance. Read this rather than inferring a cause from a poll timeout: a consumer that tracks only `status` cannot tell a provider quota cliff from a slow render.
            - `createdAt` string, date-time, required
            - `updatedAt` string, date-time, required: Use this to pick the newest record when a range has more than one.
          - `finalize` object: State of the durable MP3 finalize. The render is triggered automatically when the table read is READY. Poll GET /artifacts/{id} until `status` is `complete`, `failed`, `stalled` or `skipped`.
            - `status` 'not_finalized' | 'needs_recording' | 'needs_production' | 'queued' | 'synthesizing' | 'stitching' | 'rendering' | 'uploading' | 'stalled' | 'skipped' | 'complete' | 'failed': `needs_recording` means no completed Pipecat/Daily recording is ready to freeze; `needs_production` means a required season production element is not ready yet. `stalled` means the render stopped with nothing working on it (no progress for ten minutes and no job queued or running): POST /artifacts/{id}/finalize/retry renders it again. `skipped` means the ready read’s automatic render was never started (its cause is in `error`): ask for it again with POST /artifacts/{id}/finalize. Otherwise poll GET /artifacts/{id} until `complete`, `failed`, `stalled`, or `skipped`.
            - `finalizeUrl` string, uri, nullable: Internal staging URL used during the finalize pipeline. Not for end-user consumption; use `recordingUrl` on completion.
            - `error` string, nullable: Human-readable description of the finalize failure when `status` is `failed`, `stalled` or `skipped`.
            - `voicesOverMusic` object, nullable: The finished recording’s own measurement of its voices over the music bed, stored when audio finalize completes — measure-and-report only; it never changes a level. `null` until a production finalize measured it.
              - …
        - `video` object: Video output section. Status reflects real render state once the Remotion MP4 export has been triggered.
          - `status` 'not_rendered' | 'queued' | 'rendering' | 'complete' | 'stale' | 'needs_recording' | 'failed': Render pipeline stage. `not_rendered` until POST /artifacts/{id}/render-video or POST /artifacts/{id}/finalize?mode=video is called. `complete` = a current MP4 is available; `stale` = the read changed after the last render (re-render to refresh); `needs_recording` = finalize audio first.
          - `renderUrl` string: POST to trigger the opt-in Remotion MP4 render via the render-video endpoint (free).
          - `url` string, uri, nullable: Durable MP4 URL. Populated when `status` is `complete` (or `stale`).
        - `expiresAt` string, date-time, nullable
      - object
    - `tableReadShareLinkId` string, nullable, required
    - `creditLineItemIds` string[], required
    - `sourcePlanRevision` integer, nullable, required
    - `currentVersion` integer, required: Current manifest revision. v1 is the initial generation; each refine increments. Use ?revision=N on GET /artifacts/{id} to fetch a prior version.
    - `refineCount` integer, required: Number of times this artifact has been refined. 0 for the initial generation.
    - `failureCode` string, nullable, required
    - `failureMessage` string, nullable, required
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, 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`: The story job does not exist, was deleted, or is owned by another account.
- `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)
