# Sleeper Hit Studio — Agent Integration Guide > A story-creation and production workspace for developing, writing, performing, packaging, and publishing narrative work. Start from an initial premise, notes, research, or an existing screenplay. An existing screenplay is not required. ## 0. Start here: capability manifest and account access `GET https://sleeperhit.studio/capabilities.json` is the single authoritative capability manifest. It is versioned, unauthenticated, and CORS-open, and it covers the authenticated MCP connector, the public MCP connector, the Story API, and the CLI — with the auth requirement and required scopes stated per capability. Read it before you plan any integration. Without a Sleeper Hit account you can: - Search opted-in creator profiles, project summaries, and creator-approved public assets through the public MCP server or the public discovery API. - Read every published contract: the capability manifest, OpenAPI spec, docs site, llms.txt, and the CLI and MCP tool maps. - Open public share links (table reads, pitch decks, trailers) a creator has already published. A Sleeper Hit account is required to: - Create story projects, attach source material, and develop a project Series Bible. - Plan, generate, and revise screenplay installments, table reads, pitch decks, and trailers. - Spend Studio Credits on generation, rendering, casting, scoring, and finalization. - Publish durable releases to the first-party RSS feed and connected destinations. Anyone 18 or older can sign up at https://sleeperhit.studio/signup and accepts the Terms of Service and Privacy Policy there. An agent cannot create an account or accept the Terms on a user's behalf — give the human the signup link matching their intent and stop: - **Business and brand storytelling** — Companies, agencies, and brand or marketing teams producing narrative work. → https://sleeperhit.studio/signup?intent=business - **Independent creator** — Solo creators and small studios publishing their own shows, podcasts, and short-form video. → https://sleeperhit.studio/signup?intent=creator - **Screenwriter** — Screenwriters developing scripts, pilots, features, and pitch material. → https://sleeperhit.studio/signup?intent=writer Already signed up and approved? Sign in at https://sleeperhit.studio/login, then connect https://sleeperhit.studio/mcp. ## 1. What this API is for Sleeper Hit accepts a premise or sources (URLs, text, markdown, PDFs, scripts, and brand guides) and develops narrative deliverables through a reviewed project, Series Bible, plan, installment, performance, and packaging flow. The canonical planning abstraction is a `StoryPlan` — a source-grounded plan with thesis, evidence, narrative beats, visual system, and per-artifact render intents — not a raw prompt. Today the API ships production-stable identity, auth, credits, discovery, source ingestion, Series Bibles, persistent conversational season runs, StoryPlans, story jobs, live table reads, pitch decks, and post-creation artifact mutations. Webhooks and additional artifact adapters are landing incrementally. Read `/api/v1/capabilities` at the start of any new run so your agent adapts as new features go live instead of failing on hardcoded assumptions. ## 2. Authentication ### If you are an agent connecting a user to Sleeper Hit You cannot complete the sign-in from inside a chat or a sandboxed shell: the browser cannot reach your sandbox's callback, so your user approves and no token arrives. Do not run the login yourself, and do not retry it. Ask your user to do the one-time sign-in in their app: - **Codex:** They run `codex mcp login sleeperhit` in their own terminal, outside the agent sandbox, approve in the browser, then start a new Codex session so the tools load. - **Claude Code:** They run `/mcp`, choose sleeperhit, then Authenticate. - **ChatGPT:** They choose Connect on the app, then Authorize. After that the app renews access by itself. The app loads its tool list when a session starts, so if the tools don't appear, ask your user to start a new session. If your user can't do a browser sign-in, they can create an API key at https://sleeperhit.studio/dashboard/api (Dashboard → Developers), set it as SLEEPERHIT_API_KEY and add it as a bearer token: `codex mcp add sleeperhit --url https://sleeperhit.studio/mcp --bearer-token-env-var SLEEPERHIT_API_KEY`. Never ask your user to paste a key into the chat. Claude Code takes the same key as a header: `claude mcp add --transport http sleeperhit https://sleeperhit.studio/mcp --header "Authorization: Bearer $SLEEPERHIT_API_KEY"`. Every client's setup: https://sleeperhit.studio/mcp#api-key Verify with the `whoami` tool. It answers `source: "oauth"`, or `source: "api_key"` with the key's scopes; to create and generate, a key needs story:write and source:write. ### Preferred remote MCP path Use `https://sleeperhit.studio/mcp` for authenticated creation and production. It is an OAuth-protected Streamable HTTP MCP server and is the preferred modern Codex path. ```bash codex mcp add sleeperhit --url https://sleeperhit.studio/mcp codex mcp login sleeperhit ``` `codex mcp add` registers the server. `codex mcp login` initiates OAuth and opens or directs the user to a browser authorization page. The user runs it in their own terminal, outside any agent sandbox, and must finish sign-in and consent there. A successful OAuth exchange does not reload an already-open Codex session's tool catalog, so start a new Codex session if Sleeper Hit tools do not appear. Inspect the configured connection with `codex mcp get sleeperhit --json` or `codex mcp list --json`. The separate `https://sleeperhit.studio/api/mcp` endpoint is public, unauthenticated, and read-only. It exposes only opted-in creator profiles and creator-approved public assets; it cannot access private projects or create artifacts. After authentication, call `whoami`, `get_agent_guidance`, and `get_credits`. Read existing projects and guidance before choosing a workflow, show a plan before expensive generation, and require explicit user intent for publish, schedule, share, cancellation, deletion, or other externally visible actions. ### Alternative API-key or local stdio path Create production API keys at https://sleeperhit.studio/dashboard/api. Tokens are shown once and never displayed again — store them in your secret manager. Every request: ``` Authorization: Bearer sh__ ``` Keys are revocable and may carry an expiration. The scopes assigned at creation gate which endpoints the key can call. Initial scopes: - `story:read` - `story:write` - `source:read` - `source:write` - `artifact:read` - `artifact:publish` - `publishing:read` - `publishing:write` - `publishing:publish` - `publishing:connect` - `publishing:analytics` - `credits:read` - `webhook:write` - `account:read` - `account:write` Provider credentials (OpenAI, Anthropic, Mux, etc.) are owned by Sleeper Hit and are never required from API customers. ## 3. Discovery before integration The authenticated MCP catalog is organized into these workflow groups: - **Orientation and account:** Confirm the connected identity and load live backend guidance before choosing a workflow. Start with `whoami` → `get_agent_guidance`. Call get_credits, then list_projects or create_project. - **Credits and usage:** Check the available Studio Credit balance before any generation or rendering work. Start with `get_credits`. Show the user any plan quote before create_job, finalize_artifact, or render_artifact_video. When a job fails repeatedly, call get_generation_health before retrying — a provider chain with no reachable link cannot be fixed by another attempt. - **Your data: download it, or delete the account:** Only when the writer asks: build "Download my data" and hand them the link; or show what deleting removes and the credit balance, schedule the deletion, or cancel it. Start with `get_account_export` → `get_account_deletion`. Before a deletion, offer the export and show `credits.notice`: unused credits are forfeited at the purge, and a recent top-up should be refunded first. Tell the writer the date and that the confirmation email lets them cancel. - **Projects and source material:** Create or resume a project and ground it in notes, URLs, PDFs, research, or an existing script. Start with `list_projects` → `create_project`. Review source status, then get_series_bible or generate_series_bible. To see what the writer already has, list_project_episodes lists every episode, version and video — the ones made in the app included. Before planning, drafting, casting or creating a job — and at the start of every recurring run — read get_project and its `workspaceGate`: when `canPlan` or `canStartEpisode` is false, finish `stage` rather than calling a tool that will refuse. - **Series Bible and development:** Establish canon, format, characters, world, episode architecture, and foundational coverage. Start with `get_series_bible` → `get_mood_board` → `get_cast_canon`. Review the full Bible and coverage with the user before creating screenplay plans. A 409 `project_precondition_failed` from any tool means the project has not finished the stage in `error.details.stage` (premise, premise_coverage, planning, full_coverage): finish that stage here — do not retry the refused call, it refuses the same way until the stage is done. - **Planning and screenwriting:** Create a reviewable plan, generate an installment, inspect real pages, and revise the same artifact. Start with `create_plan` → `get_plan`. Inspect screenplay pages, then request coverage or continue into a table read. - **Pitch deck rendering:** Approve a pitch deck plan into a priced 480p render, review and choose takes chapter by chapter, then finish the deck at 720p (or 1080p) and export it. Start with `get_pitch_deck_chapters` → `approve_pitch_deck_plan`. Watch the finished master (the 720p or 1080p cut) and open the PDF before any publish or share action. - **Trailer rendering:** Approve a trailer plan into a priced 480p render, review and choose takes, then finish the cut at 720p (or 1080p) and export it. Start with `get_trailer_beats` → `approve_trailer_plan`. Watch the finished master (the 720p or 1080p cut) before any publish or share action. - **Table reads and audio:** Cast, perform, score, add effects, and create durable audio from a screenplay-backed table read. Start with `get_cast` → `get_table_read_script`. Review coverage before finalization; finalized MP3/MP4 files are durable, unlike live previews. - **Video rendering:** Direct theater motion and render a durable video from a reviewed artifact. Start with `get_artifact` → `direct_theater_motion_beat`. Inspect the completed video before any publish or share action. - **Coverage and revision:** Read existing coverage first, request analysis when needed, and revise before final packaging. Start with `get_coverage` → `get_brand_coverage`. Apply one focused revision pass, re-read pages and coverage, then finalize or package. - **Seasons and automation:** Operate controlled multi-installment season workflows. Start with `list_season_runs`. Advance one durable step at a time. Webhook administration is not currently an MCP tool. - **Recurring shows:** Set up and run a recurring podcast or show: a cast and format, a source it reads (a feed, a site, a repository, Hacker News), a schedule, spending caps the user agrees, one preview episode the user hears, then live — each episode made unattended from new source material and credited in its show notes. The user owns the show, its episodes and its feed. Start with `create_project` → `save_series_bible` → `create_publishing_series` → `preview_show_sources` → `get_show_quote` → `produce_show_episode` → `update_publishing_series`. Seven steps, in order. 1. PROJECT AND BIBLE: create_project; save_series_bible with productionMedium "audio", format, tone, audience, and the hosts as characters (name, role, voice); generate_series_bible_coverage until get_project shows workspaceGate.canStartEpisode (premise coverage opens a recurring show). 2. CAST: a voice for every host in the cast canon (list_voices, save_cast_canon) until get_project shows tableReadReadiness.ready. 3. SOURCE AND LENGTH: create_publishing_series (medium "audio", productionMode "recurring"; never cadenceEnabled at create) with showConfig.sources — rss { feedUrl } for blogs, newsletters, arXiv, NASA and archives (order "oldest" works through a feed's archive as a backlog); web-search { query } for a topic in the news; hackernews; github { repo }; url-watch { url }; upload { text }; no feed in mind? list_show_source_templates suggests some — and showConfig.pageTarget (script pages, about a minute each). Then preview_show_sources: fix any source that fails, comes back empty, or gives only short items. 4. CADENCE: cadence.schedules [{ freq daily|weekly|monthly, hour, weekday 0-6 (0 = Sunday) for weekly, dayOfMonth 1-28 for monthly, timezone, episodeType "episode" }]. 5. CAPS: get_show_quote; show the user one episode's quote and the suggested caps; set episodeCreditCap and monthlyCreditCap with userConfirmed=true only once they agree (publishPolicy "hold" if they want to hear each episode before it goes out). 6. PREVIEW: produce_show_episode without userConfirmed to quote it, then with userConfirmed=true once they agree; poll list_show_episodes until the preview is ready; give the user its release (get_publishing_release) to listen to. A preview is never published. 7. GO LIVE: only after the user has heard the preview and says go, update_publishing_series cadenceEnabled=true, userConfirmed=true — this grants the show's standing approval to produce and publish within its caps. AFTER: list_show_episodes says why any episode was skipped or failed; the user hears of every failure, three in a row pause the show (status "paused"; resume with status "active"); update_publishing_series edits sources, cadence, caps and length. - **Publishing and analytics:** Prepare destinations and releases, then publish only after the user approves the exact externally visible action. Start with `list_publishing_series` → `list_publishing_releases`. Confirm the public URL and analytics only after the user explicitly authorizes publish, schedule, cancel, or share. Before any flow, fetch the capability manifest: ```bash curl -s https://sleeperhit.studio/api/v1/capabilities \ -H "Authorization: Bearer sh__" ``` The response lists supported source types, artifact types and their current availability (`available` vs `planned`), accepted scopes, idempotency support, and links to OpenAPI + examples. Cache for at most a minute. For the full machine-readable contract: - `GET https://sleeperhit.studio/capabilities.json` — authoritative capability manifest across MCP, Story API, and CLI, with per-capability auth and scopes. No credential required. - `GET https://sleeperhit.studio/api/v1/openapi.json` — OpenAPI 3.1 spec with stable operation IDs and named JSON Schemas. - `GET https://sleeperhit.studio/api/v1/agent-guidance` — backend-managed workflow guidance. Read it at the start of every agent-run because admins can adjust it without changing the MCP/CLI binaries. - `GET https://sleeperhit.studio/agent-index.json` — docs-site page, section, operation, UI, CLI, and MCP index for agents. - `GET https://sleeperhit.studio/docs/surface-contract.json` — generated UI / Story API / CLI / MCP contract snapshot. - `GET https://sleeperhit.studio/docs/search-index.json` — searchable docs corpus with stable ids and canonical URLs. - `GET https://sleeperhit.studio/api/v1/examples` — recipes with cURL + Node snippets for live flows. ## 4. Error model Every error response is the same shape: ```json { "error": { "code": "insufficient_scope", "message": "The API key does not have the required scope for this endpoint.", "requestId": "7f9d4b6a-4f0a-4a8c-9c2e-d5b3b8e6a0f1" } } ``` Match on `code`. Human `message` text is subject to change. The same `requestId` is set in the `X-Request-Id` response header so you can quote it in support reports. Stable codes: - `authentication_required` — No bearer API key was supplied in the Authorization header. - `invalid_api_key` — The API key is malformed, unknown, or does not match the stored secret. - `api_key_revoked` — The API key has been revoked by an account owner. - `api_key_expired` — The API key has passed its configured expiration timestamp. - `account_disabled` — The account that owns this API key has been disabled. - `ip_not_allowed` — The request originated from an IP that is not in the key allowlist. - `insufficient_scope` — The credential does not include the scope required for this endpoint. The endpoint exists — `error.details.missingScopes` names what is missing. On an OAuth connector, disconnect and sign in again approving that access; on an `sh_` key, mint a new key with those scopes. - `rate_limited` — The per-key + per-IP rate limit for this route family was exceeded. - `idempotency_key_required` — The endpoint requires an Idempotency-Key header for safe retries. - `idempotency_conflict` — A request with the supplied Idempotency-Key is already processing or used incompatible parameters. - `validation_failed` — The request payload failed validation. The error message names the offending field. - `project_not_found` — The story project does not exist, was deleted, or is owned by another account. - `source_not_found` — The story source does not exist, was deleted, or is owned by another account. - `source_type_unsupported` — The requested source type is not yet supported by this API phase. Check `/api/v1/capabilities`. - `source_too_large` — The source payload exceeds the documented size limit. See `details.maxBytes`. - `source_incomplete` — A source declared as full context failed its producer-specific completeness proof and was rejected before generation. - `source_fetch_failed` — A URL or PDF source could not be safely fetched (SSRF block, redirect limit, size limit, content-type mismatch, or upstream error). - `story_plan_not_found` — The story plan does not exist, was deleted, or is owned by another account. - `story_plan_state_invalid` — The story plan is in a state that does not allow the requested operation. See `details.status` and `details.allowed`. - `story_plan_failed` — StoryPlan generation or refinement failed. See `details` for the underlying reason when available. - `season_run_not_found` — The season run does not exist, was deleted, or is owned by another account. - `season_run_state_invalid` — The season run or episode is not at the required lifecycle state. Reload it and follow `nextAction` before retrying. - `story_job_not_found` — The story job does not exist, was deleted, or is owned by another account. - `story_job_state_invalid` — The story job is in a state that does not allow the requested operation (e.g. canceling a job that has already reached a terminal state). See `details.status` and `details.allowed`. - `artifact_not_found` — The story artifact does not exist or is owned by another account. - `artifact_not_ready` — The artifact is not in a state where this operation is valid yet (e.g. requesting an MP4 render before the share link exists). See `details.status`. - `artifact_generation_failed` — The artifact adapter failed to produce a shareable output. The job and artifact rows stay inspectable; see the artifact `failureCode` / `failureMessage`. - `publishing_series_not_found` — The publishing series does not exist, was deleted, or is owned by another account. - `publishing_destination_not_found` — The publishing destination does not exist, was deleted, or is owned by another account. - `publishing_release_not_found` — The publishing release does not exist, was deleted, or is owned by another account. - `publishing_release_state_invalid` — The publishing release is in a state that does not allow the requested operation. See `details.status` and `details.allowed`. - `publishing_asset_not_ready` — The release does not have a finalized, ready asset for the requested publish operation. - `publishing_asset_stale` — The source artifact changed after the release asset was promoted. Finalize the current source before publishing. - `project_precondition_failed` — The project has not reached the development stage this call needs, so nothing was created, queued or charged. `details.stage` names the stage it has not finished (`premise`, `premise_coverage`, `planning`, `full_coverage`; `ready` when the stage is passed and a narrower precondition refused, such as a mood-board render with no image model bound), `details.missingFields` what that stage still lacks, and `details.canPlan` / `details.canStartEpisode` what the project can do now. Finish the named stage — do not retry: the identical call refuses the same way until it is done. `GET /story-projects/{projectId}` reports the same position as `project.workspaceGate` without attempting anything — read it first. - `spend_subject_unresolved` — The project gate could not find the artifact, screenplay version, episode or project this paid call is for under your account, so it refused instead of guessing — nothing was created, queued or charged. `details.subject` is the kind it looked for (`artifact`, `script_upload`, `script`, `project`) and `details.id` the id. Check the id; retrying the same one refuses the same way. - `insufficient_credits` — The account does not have enough Studio Credits to reserve this job. See `details.required` and `details.available`. A story job's refusal also carries `details.jobId`: null when the job was refused before it was created, or the id of the one job the Idempotency-Key is bound to — retrying that key after topping up resumes that job, it never creates another. - `internal_error` — The Sleeper Hit Story API failed unexpectedly. Retrying with the same Idempotency-Key is safe. - `trailer_not_found` — No trailer with that id, or it belongs to another account. - `trailer_state_invalid` — The trailer exists and the request is well-formed, but the trailer, its beat, its cast or its plates are not in a state that allows it — a plan approved outside `draft`, a take chosen or a beat rendered outside `review`/`complete`, an export before the trailer is finished, a finish while a call has no selected take, a narrated cut with no narrator, a re-voiced take selected (it is never played), a beat with no caption or footage-only when text was asked for, or a render the compile blocked. Nothing was reserved. A render refusal carries `details.refusal` = `{ code, action, fix }` — `action` is what unblocks it (`recast_figure`, `add_signature_items`, `recast_voice` or `remake_location_plate`) and `fix` names the MCP tool and CLI command that take it — plus `details.blocked[]`, one entry per beat the compile could not render. The identical request succeeds once the trailer is put right. - `pitch_deck_not_found` — No pitch deck with that id, or it belongs to another account. - `pitch_deck_state_invalid` — The deck exists and the request is well-formed, but the deck, its chapter, its cast or its plates are not in a state that allows it; `details.refusal` names the fix. State, not payload. - `pitch_deck_version_not_found` — The deck has no version with that number. Read its versions (`GET /pitch-decks/{jobId}/versions`) for the numbers it has; a version is kept each time the deck's master is published. - `trailer_version_not_found` — The trailer has no version with that number. Read its versions (`GET /trailers/{jobId}/versions`) for the numbers it has; a version is kept each time the trailer's master is published. - `story_job_not_resumable` — The job exists but has no resumable checkpoint — it either finished or failed past the point a resume can start from. - `script_not_found` — No screenplay with that id, or it belongs to another account. - `cast_precondition_failed` — A cast is not ready, so nothing was created, quoted or charged. On the casting step: nobody castable, nobody with a description, or no image model bound. On a table_read job: the project cast canon (+ the request's voiceMap) does not cover every character (`details.reason`: `no_cast_canon`, `cast_canon_collision`, `cast_canon_incomplete`), an adopted `scriptUploadId` is a draft or not cast (`script_not_finalized`, `script_not_cast_ready`), or a resumed job's own screenplay voices someone uncast (`episode_cast_incomplete`); `details.missing` lists each member and what it lacks (`voice`, and unless `details.audioOnly`, `face`, `body`, `sheet`). Complete the cast — do not retry: the identical call refuses the same way until it is done. `GET /story-projects/{projectId}` reports the canon half as `project.tableReadReadiness`. - `table_read_not_prepared` — The read's table read is not paid for yet, so nothing was started, rendered or charged. A live read, a durable audio render (finalize) and the read's sound effects and music wait until it is PREPARED — charged once per screenplay version: `POST /scripts/{scriptUploadId}/table-read/prepare` (MCP `prepare_table_read`, CLI `script table-read prepare`), or Continue to Table Read in the Writing Lab. A read made by a Story API job is paid by that job. Prepare it, then retry. - `table_read_not_failed` — The read's creative analysis is not in a FAILED state, so there is nothing to retry. Only a read whose analysis failed can be re-run (`POST /scripts/{scriptUploadId}/table-read/retry-analysis`, MCP `retry_creative_analysis`, CLI `script table-read retry-analysis`, or the Writing Lab failed card's Try Again). Nothing was changed or charged. - `voice_clone_not_found` — No voice clone with that id, or it belongs to another account. - `mood_board_full` — The mood board already holds the maximum number of frames that can bind a render. Drop one before keeping another. - `fal_account_locked` — The upstream generation provider has locked the studio account, usually for billing. Nothing was charged. - `location_plate_not_found` — The script stores no location plate at that key. Read the location plates (`GET /artifacts/{artifactId}/location-plates`) for the keys it does store; a plate the script has not made yet is made when a video renders. - `location_plate_precondition_failed` — The plate cannot be remade yet, and nothing was priced or charged: it is not ready, a remake is already running for it, the plate it is cropped from is missing, or no image model is bound. The message says which; the identical call succeeds once the plate is put right. - `voice_not_found` — No voice with that id in your library (`GET /voices` lists the ones you own), or a voice you may not use: another writer's cloned or designed voice is never previewed, cast, pinned or migrated onto. A catalog voice can be previewed and assigned, but only your own voices can be renamed or deleted. - `voice_design_not_found` — No cast voice design with that id in this project. `designId` comes from the answer of `POST /story-projects/{projectId}/cast-voice/design` for the same project. - `voice_in_use` — The voice is still pinned in a cast canon or spoken in a read, so it was not deleted. `details.uses` names where; repoint those first, or send the delete again with `confirmed: true` to delete it anyway (those characters will have no voice until they are recast). - `cast_voice_precondition_failed` — The read or project is not ready for this cast voice operation, and nothing was priced or charged: the character has no lines to speak, there is no earlier voice to restore, or the read has no finished recording to re-voice. The message says which and what to do first. - `script_in_use` — The script was not deleted: something live still depends on it. `details.uses` names each (a published or scheduled release, a running job, a season-run slot, a pitch deck or a trailer); unpublish, cancel or delete those first, then delete the script again. - `script_state_invalid` — The script's state refuses this change: the most common is deleting a script's only version, which leaves no script — delete the whole script instead (DELETE /scripts/{scriptId}). The message says which. - `plan_limit_reached` — Your plan does not allow this: an upload that would take you past the plan's Writers Lab page allowance (the message gives the pages in use and the pages this upload adds) or a second version of a script on the free plan, or a second screenplay coverage report on the free plan (it includes one). `details` carries the plan and the numbers. Upgrade the plan, or delete scripts you no longer need, then try again. - `usage_limit_reached` — This month's cap on your coverage reports (screenplay, Series Bible premise and full, outline) or table-read generations (scene music, scene atmosphere, backdrop prompts) is used up, so nothing was queued or charged. `details` carries `feature` (`COVERAGE` or `TABLE_READ`), `limit`, `used` and `resetsAt` (the first of next month, 00:00 UTC). It is state, not a rate: do not retry before `resetsAt` — the same call refuses the same way until then. - `cast_voice_casting_coverage_failed` — The candidate voice you chose FAILED its casting coverage (a model listened to the candidates and scored each against the brief), so nothing was cloned or pinned. `details.castingCoverage` carries its score, the bar, why (`flags`, `gateReasons`), the candidate it recommends instead and, when every candidate failed, a revised brief to design again from. Choose the recommendation, design again, or send `acknowledgeCastingCoverage: true` only once the writer has heard the failure and chosen it anyway. - `cast_portrait_casting_coverage_failed` — The headshot you chose FAILED its casting coverage (a model looked at the candidates and scored each against the look and the Series Bible), so nothing was pinned. `details.castingCoverage` carries its score, the bar, why (`flags`, `gateReasons`), the headshot it recommends instead and, when every one failed, a revised look to draw again from. Choose the recommendation, draw again, or send `acknowledgeCastingCoverage: true` only once the writer has seen the failure and chosen it anyway. - `voice_retired` — A voice its provider has retired (it can no longer speak) is in the way, and nothing was queued, charged or changed. A finalize, export, render, live read, re-voice or preview that would speak it refuses before anything is synthesized; a cast, pin or choose refuses to add it; a migration refuses a replacement whose provider is retiring or retired. `details.voices` names each voice (`character`, `voiceId`, `voiceName`, `provider`, `retiresAt`). Migrate those voices to a new voice (`POST /voice-migrations`; MCP `migrate_voice`; CLI `voices migrate`), then retry. - `voice_consent_required` — A CLONED voice has no consent on record from the person whose voice it is (it was made before consent records began), so nothing was cast, pinned, previewed, spoken or charged. `details.voices` names each (`voiceId`, `name`). Mint a consent link (`POST /voices/{voiceId}/consent-link`; MCP `create_voice_consent_link`; CLI `voices consent `) and give it to that person; once they confirm, retry. A Pro clone with no consent from any recording session cannot be trained. - `voice_consent_revoked` — The speaker of a cloned voice revoked their consent, so the voice is never used for anything new (work already published stays up). Nothing was cast, pinned, previewed, spoken, recorded or charged. `details.voices` names each (`voiceId`, `name`, `revokedAt`). Choose another voice; revoking is final. - `account_email_mismatch` — `confirmEmail` is not this account's email address, so no deletion was scheduled. Ask the account owner for the address on the account. - `account_deletion_refused` — This account cannot delete itself (`message` says why): it is an admin account, it hosts workshop cohorts other members belong to, or it owns a live organization (transfer or delete it first). Also returned when cancelling a deletion whose purge has already started. Nothing was changed. - `project_not_restorable` — The project cannot be restored: it is not deleted, its 30-day restore window has passed, or its purge has started (`message` says which). Its episodes are already in the library as private scripts. - `content_refused` — The request asks for, or what would be published or shared contains, content our rules never allow (sexual content involving anyone under 18, or a real person, however long ago they lived, shown in a sexual or intimate way), so nothing was made, saved, published, shared or charged. It is not retried. Rephrase or remove that content. - `safety_hold` — Publishing and sharing are paused on this account while a review is open, so nothing was published or shared. Writing is not affected. Write to help@sleeperhit.studio if you think this is a mistake. - `safety_check_unavailable` — The content safety check that runs before anything is published or shared could not run, so nothing was published or shared. Retry in a few minutes; a queued publish retries on its own. - `likeness_consent_required` — The picture or video would show a real, identifiable person who is living or died within the last 70 years (`details.people` names them). A real person's likeness needs their approval, or their estate's, so nothing was made or charged. Mint a link for them to approve (POST /likeness-consents/links; the account holder approves their own likeness in the dashboard), then retry; or describe a fictional character instead. Fictional characters, including ones who share a name with real people, and long-dead historical figures are not affected, and nobody real is ever shown in a sexual or intimate way. - `reference_image_not_owned` — A face or likeness reference (a portrait or body reference, a cast canon picture, a roster portrait) must be a picture stored in your own account: one you uploaded, or one made in your own projects. A link to anywhere else, or to another writer's picture, is refused (`details.field` names it), and nothing was changed or charged. Use a picture from your library or one of your cast's portraits. - `likeness_consent_not_found` — No likeness approval with that id belongs to this account. List them with GET /likeness-consents. - `character_not_found` — Nobody in the project's cast canon or Series Bible answers to that name (any name or alias counts). Read `GET /story-projects/{projectId}/cast-look` for the people the project has, or add the character to the Series Bible first. Nothing was changed or charged. - `portrait_design_not_found` — No portrait design with that id in this project. `designId` comes from `POST /story-projects/{projectId}/cast-portraits/design` (confirmed) or `GET .../cast-portraits/designs` for the same project. - `series_bible_version_conflict` — The Series Bible changed since the version this edit was based on (`details.expectedVersion` vs `details.currentVersion`), so nothing was saved. Read the character again and send the edit with the current version, or omit `expectedVersion` to edit whatever is saved now. - `show_cap_reached` — A recurring show's next paid step would cross one of its spending caps, so it was not taken: `details.scope` is `episode` (the episode cap), `month` (the monthly cap, in the series' timezone) or `unset` (the show has no caps, so it spends nothing). `details.cap`, `details.credits` (the step's quote) and `details.wouldSpend` give the numbers. Raise the cap with the creator (`PATCH /publishing-series/{seriesId}` `episodeCreditCap` / `monthlyCreditCap`) or wait for the next month; do not retry — the same step refuses the same way. - `not_a_sign_in_key` — A sign-out (`DELETE /device/credential`) was made with a credential no browser sign-in minted — a key made on the API page, or a connector's OAuth grant — so nothing was revoked and the credential keeps working. Revoke a key, or disconnect a connector, on the API page (`/dashboard/api`). - `not_found` — No endpoint at that path. This is a routing problem, not an authentication or method one — check the path against the API reference. - `method_not_allowed` — The path exists but does not accept that HTTP method. The `Allow` response header lists the methods it does accept. Generation-related codes (`insufficient_credits`, `story_job_not_found`, `story_job_state_invalid`, `artifact_not_found`, `artifact_not_ready`, `artifact_generation_failed`) are returned by the story-job and artifact endpoints. Additional codes will appear in the spec as remaining artifact adapters ship. ## 5. Rate limiting Requests are bucketed per API key + IP + route family. Default tier allows 120 requests per minute; elevated keys allow 600. Every response includes: - `RateLimit-Limit` - `RateLimit-Remaining` - `RateLimit-Reset` (seconds until window reset) `429` responses additionally include `Retry-After`. Back off and retry only after that many seconds. ## 6. Idempotency Any idempotent POST that reserves Studio Credits, queues generation work, or mutates an artifact requires an `Idempotency-Key` header. Generate a stable identifier per logical operation (UUID is fine). Retrying the request with the same key replays the original response with `Idempotency-Replayed: true` instead of double-charging or double-queuing. Replaying with a different request body for the same key returns `idempotency_conflict`. Recommended client behavior: 1. Generate the key once per logical job. 2. Persist the key alongside the local job record before issuing the request. 3. On network failure or 5xx, retry with the same key. 4. On `idempotency_conflict` while the original is still processing, poll for status (when polling endpoints land) instead of re-submitting. ## 7. Credits Studio Credits are the single shared meter for everything: source extraction, planning, artifact generation, refine, and finalize. Read the current balance with: ```bash curl -s https://sleeperhit.studio/api/v1/credits \ -H "Authorization: Bearer sh__" ``` Response fields: `balance`, `monthlyBucketAllocated`, `monthlyBucketUsed`, `monthlyBucketRemaining`, `perpetualBalance`, `cycleStartedAt`, `cycleExpiresAt`. Once generation endpoints ship, jobs will include a `credits` block with `quoted`, `reserved`, `settled`, and `released` so the full cost story is observable through the same API. ## 8. The polling-only happy path (table read) The minimum viable flow to produce a live table read: 1. `GET /api/v1/capabilities` — confirm `artifacts.table_read.availability` is `available`. 2. `GET /api/v1/agent-guidance` — load the current admin-managed workflow instructions for planning, approval, live audio mutation, and finalize behavior. 3. `GET /api/v1/credits` — confirm balance is plausibly enough. 4. `POST /api/v1/story-projects` — create a project with `Idempotency-Key`. 5. `POST /api/v1/story-projects/{id}/sources` — attach sources; the plan `quote` previews the credit estimate with no spend. 6. `GET|PATCH|POST /api/v1/story-projects/{id}/series-bible`, then `GET|POST /api/v1/story-projects/{id}/series-bible/coverage` — complete the project foundation and pass its versioned qualitative gate before individual script coverage. The saved Bible is auto-loaded into planning and screenplay generation. 7. `POST /api/v1/story-projects/{id}/story-plans` with `creativeBrief` and `Idempotency-Key`; follow the guidance response for the current approval gate and intake expectations. 8. Poll `GET /api/v1/story-plans/{planId}` until `REQUIRES_APPROVAL`; review `plan.plan.scriptBlueprint` with the user, then `POST /api/v1/story-plans/{planId}/approve` with `{ userConfirmed: true }` only after explicit approval. 9. `POST /api/v1/story-jobs` with the approved `storyPlanId` and `Idempotency-Key`. Credits reserve here; `402 insufficient_credits` short-circuits. 10. `GET /api/v1/story-jobs/{jobId}` every 5–15s until status is terminal (`READY` / `PARTIAL` / `FAILED` / `CANCELED`). 11. Read the `table_read` artifact manifest from the job (or `GET /api/v1/artifacts/{artifactId}`) and return the theater/audio URLs when useful. 12. Read actual script content with `GET /api/v1/artifacts/{artifactId}/script?scope=page&page=1` (or `scope=scene|character|range`) before answering questions about what was built. 13. `POST /api/v1/artifacts/{artifactId}/coverage`, poll until complete, and use the report for one revision/refine pass before finalization, pitch packaging, or later-installment work. 14. Use the refine, adaptive soundtrack, SFX, and finalize endpoints as described by `/api/v1/agent-guidance`. The full mental model: sources → Series Bible → plan → approve → job → artifact → coverage → (refine ⟳) → finalize. Agents that prefer push semantics can register webhook endpoints when that surface ships and skip polling. Both modes will be supported. ## 9. Conversational season runner Season runs turn a Series Bible episode map into resumable, sequential production without pretending that “generate a season” is one unattended operation. The durable run response is the conversation checkpoint: always reload it and follow `nextAction` after every mutation. 1. Inspect the Series Bible `document.readiness.development` result and complete an audio-appropriate Bible, then `POST /api/v1/story-projects/{projectId}/season-runs` with `Idempotency-Key`. Runtime intentionally requires only a saved episode map because the full development profile includes visual/camera fields that should not block podcasts. The header protects the HTTP replay only; durable run identity defaults to project + season number + Series Bible version, or body.runKey when supplied. Replays must use the exact same immutable create inputs. The runner imports those episodes as unconfirmed briefs and reuses or creates an audio publishing series. 2. Discuss each episode with the user. `PATCH /api/v1/season-runs/{runId}/episodes/{episodeId}` with `{ expectedMapVersion, patch, userConfirmed }`; set `patch.confirmed=true` and `userConfirmed=true` only after agreement. Reload on `season_run_state_invalid` rather than overwriting a newer map version. 3. When every brief is complete and confirmed, explicitly ask the user to lock the whole season. `POST /api/v1/season-runs/{runId}/approve` with `{ expectedMapVersion, userConfirmed: true }` requires `Idempotency-Key`. Send the mapVersion from the exact response the user reviewed; a stale value is rejected and must be re-reviewed. Approval locks further map edits and requires repeatable non-FREE coverage access. 4. Call `POST /api/v1/season-runs/{runId}/advance` with a stable `Idempotency-Key` for one durable step at a time. Poll with GET between steps. Omit action for the advance default; any supplied value outside advance, replan, retry_coverage, revise, or publish returns validation_failed instead of being coerced. The runner processes episodes in order. 5. At `nextAction.type=approve_plan`, review the generated StoryPlan with the user and call the existing `POST /story-plans/{planId}/approve` with `{ userConfirmed: true }`; advancing the season does not approve it for them. At `nextAction.type=replan`, a plan is terminally failed/rejected/superseded: obtain user approval before advance `{ action: "replan", instruction?: "exact correction brief", userConfirmed: true }` creates a replacement while retaining attempt history. When instruction is omitted for a rejected plan, the persisted rejection reason becomes the replacement StoryPlan creativeBrief.replanInstruction. The pending instruction survives dispatch failure and clears only after the replacement is durably attached. 6. Coverage is a hard, fail-closed gate before durable MP3 finalization. A terminal generation failure returns `nextAction.type=retry_coverage`; after explicit approval, call advance with `{ action: "retry_coverage", userConfirmed: true }`. This retries the same current CoverageReport id/version and does not create or meter a second report. For a completed report, overallScore must be at least 8/10; premise, structure, characters, dialogue, pacing, and marketFit must each be at least 7/10; recommendation must be RECOMMEND or CONSIDER. These score and recommendation rules are hard floors: custom policy can be stricter but cannot lower a score threshold or admit PASS. Missing, non-integer, or out-of-range scores fail. 7. At `nextAction.type=revise`, show the failure reasons and proposed instruction, agree on exact language with the user, then call advance with `{ action: "revise", instruction, userConfirmed: true }`. Every attempt and approved instruction remains in the audit history. Revision attempts default to three, but a season may set any positive integer when the user wants to keep spending credits on approved revisions. 8. Coverage success triggers durable audio finalization. At `nextAction.type=repair_production`, inspect `artifactId` with `get_artifact` and follow the durable blockers. Repair music with `get_music`/`generate_music`/`update_music`, SFX with `list_sfx`/`add_sfx`/`update_sfx`, and voice effects with `modify_voice` as applicable, then call advance with `{ action: "advance" }` to re-evaluate the same artifact. 9. At `nextAction.type=publish`, ask the user before calling advance with `{ action: "publish", userConfirmed: true }`; only then is the episode promoted into the first-party RSS feed. Spotify does not need a second audio upload: an externally hosted show follows the Sleeper RSS feed. The one-time ownership claim still happens in Spotify for Creators; Sleeper can initiate that handoff and record the resulting show URL, but it does not claim that Spotify account or show silently through these API routes. ## 10. Versioning The current API base is `/api/v1`. Breaking changes ship behind a new base prefix. Additive changes (new endpoints, new optional fields, new artifact types, new scopes) land inside `/api/v1` and are reflected in `/api/v1/capabilities` and `/api/v1/openapi.json` immediately. ## 11. Where to look next - Capability manifest (public, all surfaces): https://sleeperhit.studio/capabilities.json - Live feature manifest (authenticated): https://sleeperhit.studio/api/v1/capabilities - OpenAPI 3.1 spec: https://sleeperhit.studio/api/v1/openapi.json - Examples: https://sleeperhit.studio/api/v1/examples - Short doc map for agents: https://sleeperhit.studio/llms.txt - API key management: https://sleeperhit.studio/dashboard/api