# Sleeper Hit Studio Story API > Authenticated B2B API for programmatic story generation. Submit sources and constraints, get back narrative deliverables (pitch deck video + PDF, table read, storyboards, trailers, production video). Designed for AI agents and enterprise systems. Status: production-stable identity, auth, credits, discovery, source ingestion, project Series Bibles, StoryPlans, and story jobs (with the live table read artifact). Remaining artifact adapters and webhooks ship in follow-on phases. Always read `/api/v1/capabilities` before integrating a flow. ## Discovery - [OpenAPI 3.1 spec](https://docs.sleeperhit.studio/api/v1/openapi.json): full machine-readable contract — operation IDs, schemas, security, error model. - [Capabilities manifest](https://docs.sleeperhit.studio/api/v1/capabilities): live feature availability (auth, sources, artifacts, docs). - [Agent guidance](https://docs.sleeperhit.studio/api/v1/agent-guidance): backend-managed workflow guidance for Story API, CLI, and MCP agents, including MCP server instructions plus tool/resource descriptions. Read this before creating or refining table reads. - [Examples](https://docs.sleeperhit.studio/api/v1/examples): cURL + Node recipes for the polling-only happy path, error recovery, and idempotent POSTs. - [Docs site](https://docs.sleeperhit.studio/docs): crawlable human docs plus API / CLI / MCP maps. - [Agent docs index](https://docs.sleeperhit.studio/agent-index.json): page, section, operation, CLI, and MCP discovery index for agents. - [Agent integration guide](https://docs.sleeperhit.studio/llms-full.txt): long-form prose for agents — auth, idempotency, rate limits, error recovery, what is and is not live. ## Authentication - Create production API keys at https://docs.sleeperhit.studio/dashboard/api. Tokens are shown once. - Send `Authorization: Bearer sh__` on every request. - Keys are scoped. Initial scopes: `story:read`, `story:write`, `source:read`, `source:write`, `artifact:read`, `artifact:publish`, `credits:read`, `webhook:write`. - Provider keys (e.g. OpenAI) are managed separately by Sleeper Hit and are not exposed to API customers. ## Conventions - Errors use a stable envelope: `{ error: { code, message, requestId } }`. Match on `code`, not `message`. - Per-request `X-Request-Id` is echoed in response headers and inside error envelopes — quote it when reporting issues. - Rate limit headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`) are returned on every response. `429` responses include `Retry-After`. - Idempotent POSTs require `Idempotency-Key`. Retry-safe with the same key; the API replays the original response. - Studio Credits are the single shared meter. Read balance from `/api/v1/credits` before triggering a job. ## Endpoints (live today) - `GET /api/v1/capabilities` — requires scope `story:read`. Capability manifest. - `GET /api/v1/agent-guidance` — requires scope `story:read`. Current backend-managed agent workflow guidance. - `GET /api/v1/credits` — requires scope `credits:read`. Studio Credit summary. - `POST /api/v1/story-projects` — `story:write`. Create a project workspace. - `GET /api/v1/story-projects` — `story:read`. Cursor-paginated project list. - `GET|PATCH|DELETE /api/v1/story-projects/{id}` — read, update, soft-delete a project. - `GET|PATCH|POST /api/v1/story-projects/{id}/series-bible` — read, save, or sidecar-generate the project-level Series Bible used as durable canon across UI/API/CLI/MCP. - `POST /api/v1/story-projects/{id}/sources` — `source:write`. Attach inline text, markdown, URL, or PDF. - `GET /api/v1/story-projects/{id}/sources` — list sources for a project. - `GET|DELETE /api/v1/story-projects/{id}/sources/{sourceId}` — read or soft-delete a single source. - `POST /api/v1/story-projects/{id}/story-plans` — `story:write`. Generate a StoryPlan from sources + target + artifact requests. - `GET /api/v1/story-projects/{id}/story-plans` — list plans. - `GET /api/v1/story-plans/{planId}` — read a single plan. - `POST /api/v1/story-plans/{planId}/approve` and `/reject` — `story:write`. Approve/reject after `REQUIRES_APPROVAL`. - `POST /api/v1/story-jobs` — `story:write`. Generate artifacts from an APPROVED plan; reserves Studio Credits and enqueues the runner. - `GET /api/v1/story-jobs` and `GET /api/v1/story-jobs/{jobId}` — `story:read`. List + poll jobs (status, progress, artifacts). - `POST /api/v1/story-jobs/{jobId}/cancel` — `story:write`. Cancel a `PENDING` or `RESERVED` job and release reserved credits. Requires `Idempotency-Key`. - `GET /api/v1/story-jobs/{jobId}/artifacts` and `GET /api/v1/artifacts/{artifactId}` — `artifact:read`. Read generated artifacts + their manifest URLs. Add `?revision=N` to fetch a prior revision. - `POST /api/v1/artifacts/{artifactId}/render-video` — `artifact:publish`. Opt-in MP4 render for a table read (separate charge). Requires `Idempotency-Key`. - `POST /api/v1/artifacts/{artifactId}/refine` — `artifact:publish`. Conversational in-place revision. Requires `Idempotency-Key`. Body: `{ instruction, scope? }`. Returns `{ storyJob }` to poll. Reserves TABLE_READ_REFINE (8 credits). Artifact id + share URLs stay stable. - `POST /api/v1/artifacts/{artifactId}/voice` — `artifact:publish`. Recast one character's voice in place (no new revision, no charge). Requires `Idempotency-Key`. Body: `{ character, voiceId, voiceName, gender?, provider? }`. Returns the updated `voiceMap`; the cast is readable on `manifest.audio.voiceMap`. - `POST /api/v1/artifacts/{artifactId}/avatar` — `artifact:publish`. Regenerate ONE character's avatar portrait in place (no new revision, no charge). Requires `Idempotency-Key`. Body: `{ character, refinement?, style?, referenceImageUrl? }`. Async + queue-backed: returns `{ artifactId, character, status, chatToolJobId }`; poll `GET /api/v1/artifacts/{artifactId}/cast` and watch that character's `avatarStatus` (`queued` → `rendering` → `ready`). - `POST /api/v1/artifacts/{artifactId}/cast` — `artifact:publish`. Batch-update the cast in one call (no new revision, no charge). Requires `Idempotency-Key`. Body: `{ entries?: [{ character, voiceId?, voiceName?, gender?, provider?, avatarRefinement?, regenerateAvatar? }], avatarStyle? }` — provide at least one `entries` item OR a top-level `avatarStyle` (which restyles ALL portraits). Voice reassignments apply immediately; returns the updated `voiceMap` plus one async avatar op `{ character, status, chatToolJobId }` per queued render — poll `/cast` until each `avatarStatus` is `ready`. - `GET /api/v1/artifacts/{artifactId}/cast` — `artifact:read`. Read the enriched cast: each character's voice (`voiceId` / `voiceName` / `gender` / `provider`) merged with its avatar (`avatarUrl` / `avatarStyle` / `avatarStatus`), plus the resolved cast-wide `avatarStyle`. `avatarStatus` is `none` | `queued` | `rendering` | `ready` | `failed` — poll after recast_avatar / update_cast until in-flight renders reach `ready`. - `POST /api/v1/artifacts/{artifactId}/voice-modification` — `artifact:publish`. Apply a voice effect (autotune) to a contiguous range of dialogue entries in place (no new revision, no charge). Requires `Idempotency-Key`. Body: `{ startEntryIndex, endEntryIndex, effect?, params? }`; the autotune `params` are optional/partial and merged over the defaults. Returns `{ artifactId, modificationId, status }`; rendered audio is projected onto the read once `status` is `ready`. - `POST /api/v1/artifacts/{artifactId}/finalize` — `artifact:publish`. Durable render. Requires `Idempotency-Key`. Body: `{ mode? }` — `"audio"` (default, 6 credits) or `"video"` (12 credits). Replay-safe. Poll GET artifact for `manifest.audio.finalize.status` (audio) or `manifest.video.status` (video). - `POST /api/v1/artifacts/{artifactId}/coverage` — `story:write`. Generate professional script coverage for the screenplay backing a table read. Requires `Idempotency-Key`. Body: `{ focusPrompt? }`. Returns `{ reportId, status }`; poll `GET /api/v1/artifacts/{artifactId}/coverage`. Free plan: one report. - `GET /api/v1/artifacts/{artifactId}/coverage` — `artifact:read`. Read the latest coverage report (or `?reportId=`). The report carries a `status` (`generating` → `complete` / `failed`); `report` holds the structured payload on completion. - `POST /api/v1/artifacts/{artifactId}/music` — `artifact:publish`. Generate/regenerate adaptive per-scene music in place with optional `{ musicCoveragePercent? }`, or update one scene with `{ sceneIndex, prompt?, summary?, enabled?, weight? }`. Requires `Idempotency-Key`. Poll GET `/music` until `music.status` is `ready`. - `POST /api/v1/artifacts/{artifactId}/sfx` — `artifact:publish`. Add or remove timed sound-effect cues in place. Requires `Idempotency-Key`. Add body: `{ op: "add", entryIndex, label, prompt, volume?, triggerOffset? }`; remove body: `{ op: "remove", id }`. - `GET /api/v1/artifacts/{artifactId}/sfx` — `artifact:read`. List timed sound-effect cues. ## Source ingestion semantics - `text` and `markdown`: extract synchronously, return `status: READY` with `extractedTextPreview`, `contentHash`, `byteSize`, `tokenEstimate`. - `url`: validated synchronously; fetch + extract runs async in a pg-boss worker. Returns `status: PENDING` immediately. Poll the source until `status: READY` (or `FAILED` with `failureCode` / `failureMessage`). The fetcher enforces http/https only, blocks private / link-local / cloud-metadata IPs, caps redirects (3) and body size (10 MB), and accepts content types `text/html`, `text/plain`, `text/markdown`, `application/json`, `application/xhtml+xml`. - `pdf`: same SSRF + redirect + size guards as `url`. Requires upstream `Content-Type: application/pdf`. Worker extracts text via pdfjs-dist and records `metadata.pageCount`. Poll until terminal. ## Source digest Once a source reaches `status: READY` and has extracted text, the API queues an LLM digest pass behind the DB sidecar `story_api_source_digest`. The digest is structured-output only — no freeform JSON from the model. - Poll `digestStatus` on the source. States: `NOT_STARTED`, `PENDING`, `RUNNING`, `READY`, `FAILED`, `SKIPPED`. - When `digestStatus: READY`, `digest` carries `{ summary, thesis, claims[], evidence[], gaps[], rightsNotes[] }`. Claims are typed (`fact`/`estimate`/`opinion`/`forecast`/`quotation`/`definition`) and may carry an `attribution` string. - `SKIPPED` is the expected state when the sidecar/provider is not bound in your environment, when retention is `METADATA_ONLY`, or when the extracted text is too short — the underlying source is still usable. - `FAILED` keeps the source available but signals the digest itself errored. Inspect `digestFailureCode` + `digestFailureMessage`. ## StoryPlan The StoryPlan is the canonical source-grounded planning abstraction for the Story API. It is generated asynchronously via DB sidecar `story_api_plan` with structured Zod-validated output — never freeform JSON from the model. For multi-installment work, use the project Series Bible before each StoryPlan. GET/PATCH/POST `/story-projects/{id}/series-bible` keeps format, audience, canon, episode map, characters, visual style, and audio direction in one project-level document shared by the web UI, Story API, CLI, and MCP. POST generation is backed by the DB sidecar `story_api_series_bible`. - Create with POST `/story-projects/{id}/story-plans`, body `{ target, artifactRequests, sourceIds?, styleConstraints?, autoApprove? }`. Any `sourceIds` must already have `status: READY`. For `table_read`, `artifactRequests[].narrationPolicy` may be `auto`, `include`, or `suppress`; ask/preserve this when narrator/no-narrator matters instead of inferring it from mode alone. - Poll `status` on the plan. States: `PENDING`, `RUNNING`, `REQUIRES_APPROVAL`, `READY`, `APPROVED`, `REJECTED`, `FAILED`, `SUPERSEDED`. - When `autoApprove: true`, the worker lands the plan at `APPROVED` directly. Otherwise the worker lands at `REQUIRES_APPROVAL` and the customer must POST `/approve` or `/reject`. - `plan` field carries the canonical envelope: `{ version: 1, projectId, title, target, sourceDigest, thesis, journey: { beats }, visualSystem, audioSystem?, artifacts }`. Beats use the role enum `hook | context | conflict | proof | turn | payoff | cta`. - `quote` carries a deterministic Studio Credits estimate. Planning and source digest line items are included/unmetered with zero credits; artifact and add-on line items are reserved and settled when jobs/refines/finalizes run. - Source provenance is preserved: `sourceIds[]` on the plan record + `sourceDigest.evidence[].sourceId` inside the envelope. - Failures land at `status: FAILED` with `failureCode: story_plan_failed` and a human-readable `failureMessage` (sidecar missing, provider missing, generation error). The plan row stays inspectable for triage. ## Story jobs + Artifacts A story job runs queued artifact generation against an APPROVED StoryPlan. Table reads and pitch decks are live adapters; storyboard, trailer, and production video are still planned (check `/api/v1/capabilities.artifacts`). - Create with POST `/story-jobs`, body `{ storyPlanId, artifactRequests?, projectId? }`. The plan must be `APPROVED`. `artifactRequests` defaults to the plan's own requests; if supplied it overrides them (max 6). For table reads, preserve or set `narrationPolicy` (`auto | include | suppress`) when overriding the request. Studio Credits for the artifact line items are reserved at create time — a `402 insufficient_credits` (with `details.required` / `details.available`) means the reservation could not be covered. - Poll `status` on the job. States: `PENDING`, `RESERVED`, `RUNNING`, `PARTIAL`, `READY`, `FAILED`, `CANCELED`. `PARTIAL` means at least one artifact is READY while others are still running. Credits settle when an artifact becomes shareable. - Cancel a pre-run job (`PENDING | RESERVED`) with POST `/story-jobs/{jobId}/cancel`; reserved-but-unsettled credits are released. Requires `Idempotency-Key`. - Read artifacts via GET `/story-jobs/{jobId}/artifacts` or GET `/artifacts/{artifactId}`. Artifact states: `PENDING`, `RUNNING`, `READY`, `FAILED`. A failed adapter surfaces `artifact_generation_failed` with the artifact `failureCode` / `failureMessage`. Add `?revision=N` to GET `/artifacts/{artifactId}` to fetch a prior revision. - Refine with POST `/artifacts/{artifactId}/refine` (scope `artifact:publish`, Idempotency-Key required). Body: `{ instruction, scope? }`. Returns `{ storyJob }` to poll. TABLE_READ_REFINE costs 8 credits. The artifact id, share token, and player URLs are stable across refines. - Finalize with POST `/artifacts/{artifactId}/finalize` (scope `artifact:publish`, Idempotency-Key required). Body: `{ mode? }` — `"audio"` (default, TABLE_READ_FINALIZE_AUDIO, 6 credits) or `"video"` (TABLE_READ_FINALIZE_VIDEO, 12 credits). Replay-safe. Poll GET artifact for `manifest.audio.finalize.status` (audio) or `manifest.video.status` (video). ### Pitch deck artifact shape A `pitch_deck` artifact is generated from a completed table-read-backed script so it can reuse the canonical cast avatars and voices. When `status: READY`, the artifact `manifest` carries: - `pitchDeckJobId` — the underlying journey-plan pitch deck job. - `share.url` — the public pitch-deck page. - `video.status`, `video.url`, `video.durationMs` — the rendered pitch-deck video. - `pdf.status`, `pdf.url` — the matching PDF companion deck. - `audio.voiceMap` — table-read character voices reused for narration. ### Table read artifact shape A `table_read` artifact is a live, shareable performance — not a static file. Modes: `documentary | podcast | drama`; `narrationPolicy` controls whether a NARRATOR role is expected and is not implied by mode alone. When `status: READY`, the artifact `manifest` carries: Ask for the narrator choice explicitly. Narrator/no-narrator is independent of mode: podcasts, documentaries, dramas, panels, audio essays, and casefile reads can each be narrated or narrator-free when that fits the piece. - `shareToken` — an expiring JWT. This token (not your customer API key) authorizes the returned URLs, so you can embed them or hand them to end-users ("unlisted", like an unlisted video). - `theaterUrl` — the framed visual theater-mode page (`/share/table-read/{token}`), with attribution chrome. - `theaterFullscreenUrl` — the same theater stage rendered immersive and chrome-free (`?view=fullscreen`). Best for embeds, kiosk, or handing an end-user a distraction-free full-window experience. - `audio.liveUrl` — a minimal-chrome, audio-only player page (`/share/table-read-audio/{token}`). The end-user clicks play and the live read streams as audio (no visual stage). Use this when you only need sound. - `audio.startLiveUrl` with `audio.mode: "live_on_demand"` — the raw endpoint both players use: POST `{ token }` to spin up an on-demand Pipecat/Daily live read; it returns `{ dailyRoomUrl }` the end-user joins. No prior recording is required; the read is live-performable from creation. - `audio.recordingUrl` and `audio.recordingDurationMs` — populated after a successful audio finalize based on a completed Pipecat/Daily table-read recording (see below). `null` until then. - `audio.finalize` — present after POST `/artifacts/{artifactId}/finalize` is called with `mode:"audio"`. Fields: `{ status, finalizeUrl, error }`. `status` is `needs_recording` until a completed Pipecat/Daily recording exists, then progresses through `queued → rendering → uploading → complete` (or `failed`). Poll GET `/artifacts/{artifactId}` until terminal. - `video` — the OPT-IN MP4. `video.status` is `not_rendered` by default; trigger with POST `/artifacts/{artifactId}/render-video` or POST `/artifacts/{artifactId}/finalize` with `{ mode: "video" }`. On completion: `video.status: "complete"` and `video.url` populated. - `currentVersion` and `refineCount` — versioning fields on the artifact. v1 = initial generation; each refine increments. Fetch a prior revision with GET `/artifacts/{artifactId}?revision=N`. - `expiresAt` — when the share token (and thus the player URLs) expire. ### Refine (post-creation revision) POST `/artifacts/{artifactId}/refine` (scope `artifact:publish`, requires `Idempotency-Key`) Body: `{ "instruction": "", "scope"?: "auto" | "screenplay" | "plan" }` (default scope `"auto"`). - `"screenplay"` — rewrites the screenplay + recasts voices in place (faster, most instructions land here). - `"plan"` — re-grounds the StoryPlan against sources then re-adapts (use for major structural or thematic changes). - `"auto"` — model self-routes. Returns `{ storyJob: { id, status, ... } }`. Poll GET `/story-jobs/{jobId}` until `READY` or `FAILED`, exactly like a standard artifact job. The artifact id, share token, and all three player URLs are STABLE — embed once and the updated read appears automatically. Reserves TABLE_READ_REFINE (8 Studio Credits) at job create time. ### Finalize (durable render) POST `/artifacts/{artifactId}/finalize` (scope `artifact:publish`, requires `Idempotency-Key`) Body: `{ "mode"?: "audio" | "video" }` (default `"audio"`). - `"audio"` (6 credits — TABLE_READ_FINALIZE_AUDIO) — freezes the current completed Pipecat/Daily table-read recording into a durable MP3. If no completed recording exists yet, finalize returns `needs_recording` and does not reserve credits. Poll GET artifact: `manifest.audio.finalize.status` (`needs_recording`, or `queued → rendering → uploading → complete`). On complete: `manifest.audio.recordingUrl` (MP3) and `manifest.audio.recordingDurationMs`. - `"video"` (12 credits — TABLE_READ_FINALIZE_VIDEO) — queues the Remotion MP4 export. Poll GET artifact: `manifest.video.status` and `manifest.video.url`. Replay-safe: re-calling finalize when already complete or in-flight returns the current status without re-charging. ### Coverage (script analysis) POST `/artifacts/{artifactId}/coverage` (scope `story:write`, requires `Idempotency-Key`) Generates professional script coverage for the screenplay backing a `table_read` artifact: logline, premise, structure, characters, dialogue, pacing, market fit, an overall score, top fixes, strengths, comparables, and a recommendation. Generation is async + queued. Body: `{ "focusPrompt"?: "" }`. Returns `{ artifactId, reportId, status, version }` with `status: "generating"`. Poll GET `/artifacts/{artifactId}/coverage` (scope `artifact:read`, optionally `?reportId=`) until `status` is `complete` or `failed`. On `complete`, `report` carries the structured coverage payload; it is `null` while generating. GET returns `{ coverage: null }` when no coverage has been requested yet. Coverage produces a separate report and never changes the artifact, its revision, or its share URLs. Free-plan accounts may keep one coverage report; further requests return `validation_failed` until you upgrade. ### Adaptive music (live scoring) POST `/artifacts/{artifactId}/music` (scope `artifact:publish`, requires `Idempotency-Key`) Generates per-scene adaptive music for the read backing a `table_read` artifact, or updates one scene music direction when `sceneIndex` is supplied — the same live scoring the in-app studio uses. This mutates the live read in place (no new revision; same artifact id and share URLs). Generation body: `{ "musicCoveragePercent"?: number }`. Accepts a fraction (0..1) or a whole percent (0..100); it is clamped into [0,1]. When supplied, the read is (re)scored and that share of scenes is set to carry music; when omitted, per-scene music is (re)generated without changing coverage. Returns `{ artifactId, status: "generating", coveragePercent, totalScenes }`. Scene edit body: `{ "sceneIndex": number, "prompt"?: string, "summary"?: string, "enabled"?: boolean, "weight"?: number }`. Use this for requests like "make scene 2 heavier bass" or "mute the cold open." Returns `{ artifactId, status: "ready", sceneIndex, adaptiveSceneDirection }`, keeps share URLs stable, and marks any finalized MP3/video stale until explicit finalize. Poll GET `/artifacts/{artifactId}/music` (scope `artifact:read`) until `music.status` is `ready` for music-bearing reads or `none` for intentional no-music reads. GET returns `{ music: { status, scenesWithMusic, totalScenes, coveragePercent } }` where `status` is `none` (no active music, including 0% coverage), `generating` (scoring is still in progress), or `ready` (at least one scene carries music). The artifact manifest `audio.adaptiveMusic` flag also reflects readiness. ### Sound effects POST `/artifacts/{artifactId}/sfx` (scope `artifact:publish`, requires `Idempotency-Key`) Adds, updates, or removes timed sound-effect cues on the live read backing a `table_read` artifact. This mutates the same read in place: no new artifact revision, same artifact id, same share URLs. Add body: `{ "op": "add", "entryIndex": number, "label": string, "prompt": string, "volume"?: number, "triggerOffset"?: "before" | "with" | "after" }`. Update body: `{ "op": "update", "id": string, "entryIndex"?: number, "label"?: string, "prompt"?: string, "volume"?: number, "triggerOffset"?: "before" | "with" | "after", "regenerate"?: boolean }`. Remove body: `{ "op": "remove", "id": string }`. GET `/artifacts/{artifactId}/sfx` (scope `artifact:read`) returns `{ sfx: { artifactId, cues } }`. Each cue has `{ id, label, entryIndex, soundUrl, isDraft }`; `soundUrl` can be null while reusable sound generation is not ready. ### Voice modification POST `/artifacts/{artifactId}/voice-modification` (scope `artifact:publish`, requires `Idempotency-Key`) Applies a voice effect (currently only `autotune`) to a contiguous range of dialogue entries on the live read backing a `table_read` artifact. This mutates the same read in place: no new artifact revision, same artifact id, same share URLs. Body: `{ "startEntryIndex": number, "endEntryIndex": number, "effect"?: "autotune", "params"?: { "key"?, "scale"?, "strength"?, "smooth"?, "reverb"? } }`. Indices are inclusive integers >= 0. `effect` defaults to `"autotune"`. The autotune `params` recipe is optional and partial — any omitted field falls back to the proven defaults: `key` `D`, `scale` `minpent`, `strength` `1.0`, `smooth` `1`, `reverb` `chapel`. Allowed values: `key` ∈ `C C# D D# E F F# G G# A A# B`; `scale` ∈ `major | minor | majpent | minpent | chromatic`; `strength` is a number `0..1`; `smooth` is an integer `>= 1`; `reverb` ∈ `none | light | chapel`. Async + queued: returns `{ artifactId, modificationId, status }` where `status` is `queued | rendering | ready | failed`. The rendered audio is projected onto the read's entries once the modification reaches `ready`. ## Endpoints (planned) - Storyboard, trailer, and production video artifact adapters: planned. The table read and pitch deck adapters are live today. - Webhooks: signed deliveries for job and artifact lifecycle events. ## Optional - [Product docs](https://docs.sleeperhit.studio) - [API key management](https://docs.sleeperhit.studio/dashboard/api)