Skip to content

Sleeper Hit makes produced episodes: the script, the voices and the mix.

Sleeper Hit Docs

Publishing

Promote a finished artifact into a release, schedule it, and publish it to a first-party RSS feed or a connected YouTube channel or TikTok account.

View as Markdown
On this page (7 sections)

Publishing is the project-level distribution layer for Sleeper Hit Studio. It does not create or refine MP3/MP4 media. Source surfaces such as Table Read, Pitch Deck, Storyboarding, or later video tools create durable StoryArtifact assets; Publishing promotes those ready artifacts into releases, schedules them, publishes them to destinations, and tracks status.

Two kinds of destination ship. Podcast/RSS publishes the finalized Table Read MP3 already stored on StoryArtifact.manifest.audio.recordingUrl; a video release publishes its own rendered MP4 to a connected YouTube channel or TikTok account (see Video Destinations below).

Ownership Model

  • StoryProject owns one or more PublishingSeries rows.
  • PublishingSeries stores medium, format, prompt direction, podcast type/category metadata, feed metadata, owner metadata, its timezone, its standing approval, and provider destinations.
  • A PublishingSeries is also the platform's show. Its production is the show sub-resource (/publishing-series/{seriesId}/show: its setup, schedule, caps, on/off and episodes), which is how a series makes its own episodes on a schedule instead of only publishing ones made elsewhere. There is no separate show model: see docs/shows.md. A show's episodes are never in Ready to publish: the show publishes its own.
  • PublishingRelease stores one episode/release under a series and keeps links back to StoryProject, Series Bible document, StoryPlan, StoryJob, and the promoted StoryArtifact.
  • PublishingReleaseAsset stores the bound durable asset URL, media metadata, and source fingerprint.
  • PublishingReleaseTarget stores destination-specific status: the feed item for RSS, and the upload, its recorded external id and its status for each connected provider.
  • PublishingProviderConfig stores the account's connection to a provider — the tokens of a connected YouTube or TikTok account, encrypted and never read back — with the scopes the account granted.
  • PublishingAnalyticsSnapshot stores normalized provider/feed metrics.

Episode Promotion

Publishing v1 starts after Table Read refinement is complete:

  1. The writer refines the episode in Table Read.
  2. The existing artifact finalize path durably renders the MP3.
  3. The writer promotes the finalized StoryArtifact into a PublishingRelease.
  4. Publishing snapshots the MP3 URL, duration, source fingerprint, and metadata into PublishingReleaseAsset.
  5. The writer schedules, cancels, or publishes the release.
  6. RSS publication makes the published release visible in the first-party feed.

Promotion, scheduling, or publish means the user considers the source artifact ready. There is no separate publishing-side draft, generation, review, or approval loop in v1.

RSS V1

The first-party RSS provider uses:

  • series metadata from PublishingSeries
  • episode metadata from PublishingRelease
  • duration from the ready PublishingReleaseAsset; the enclosure URL is the episode download route below, never the stored file
  • public feed route GET /api/v1/publishing-feeds/{seriesSlug}.xml
  • public episode download GET /api/v1/publishing-feeds/{seriesSlug}/episodes/{releaseId}.mp3 (BL-3196): counts the download and 302s to the ready asset's file; in the OpenAPI spec and llms.txt like the feed

RSS does not require OAuth. The feed includes Apple-compatible podcast type and category metadata from the series. Apple Podcasts and Spotify can be tracked as destinations around that feed. An audio release is published by its feed alone: the providers that accept a submission are the video destinations below.

Video Destinations: YouTube and TikTok

A video release publishes its own rendered MP4 to a provider the writer has connected, in the same publish pass that completes its RSS target. Two ship:

  • YouTube uploads the MP4 to the connected channel with the resumable upload protocol, as an unlisted video that carries containsSyntheticMedia. Connecting goes through GET /api/v1/publishing/youtube/connect (scope https://www.googleapis.com/auth/youtube.upload) and its callback, which stores the tokens. A publish with no connected channel is refused with "This destination has no connected YouTube account."
  • TikTok uploads the MP4 through the Content Posting API v2 (push_by_file, so no domain verification is required). Two modes, chosen by the scopes the connected account granted: a draft to the account's inbox (video.upload, which works before TikTok has audited the app) or a direct post (video.publish, which TikTok only allows once that audit has passed). The publish polls the post's status until it is terminal. Connecting goes through GET /api/v1/publishing/tiktok/connect and its callback.

What each destination needs: a connected account for that provider, and a release whose durable asset is the rendered MP4 — the publisher fetches that file, so a release with no rendered video is refused, never posted. A retried publish resumes from the recorded upload id and never posts the same video twice.

FIRST THE APP, THEN THE WRITER: YouTube and TikTok sign in through Sleeper Hit's own app for that platform, configured once for the whole platform (server/publishing-oauth.ts). Until that app is set up, the provider is not shown to writers at all — no destination row, no connect control, and a connect attempt is refused in one sentence (server/publishing-provider-availability.ts) — so a destination that names it cannot be completed until the app exists.

Four Surfaces

Publishing operations are exposed through:

  • Writers Lab Publishing tab
  • Story API /api/v1 routes and OpenAPI
  • sleeperhit publishing ... CLI commands
  • MCP publishing tools

The operation registry, capabilities manifest, generated surface contract, CLI metadata, and MCP schemas must stay aligned. Mutating API operations require idempotency where retrying could duplicate rows or jobs.

In Slug

Slug reaches the same publishing cores as the other surfaces. In a conversation bound to a project, on a paid workspace, Slug carries 16 publishing tools — the ones MCP serves under the same names:

  • list_publishing_series, create_publishing_series, update_publishing_series
  • list_publishing_destinations, upsert_publishing_destination
  • list_publishing_ready_recordings, remove_publishing_ready_recording
  • list_publishing_releases, get_publishing_release, create_publishing_release, update_publishing_release
  • generate_publishing_release_description, refresh_published_release_media
  • schedule_publishing_release, publish_publishing_release, cancel_publishing_release

A create takes title, description, medium (audio / video / short_form), format, language and coverArtUrl. An update takes those, plus the fields that decide how the show is found and filed: visibility (public / unlisted / private), timezone (an IANA zone its schedule runs on), slug (its address and feed, which a show with published episodes keeps), podcastCategory and podcastSubcategory (Apple's list, the same table a write validates against), podcastType (episodic / serial) and explicitContent, plus status (draft / active / paused / archived). The synthetic-disclosure line, rating, copyright, owner contact, prompt direction, metadata, the standing-approval key, the credit caps and the publish policy are never chat inputs — recurring shows stay with the show tools, and standing approval and caps stay with the UI, Story API, MCP and CLI.

Two of the sixteen switch a show's video destinations. list_publishing_destinations reads which accounts a show's videos go to and which are on: a video destination with status connected is on, archived is off. upsert_publishing_destination makes that change — provider is youtube (widescreen videos) or tiktok (vertical videos), and status is connected or archived. Turning one back on needs the account it was connected with, so with no connected account the call refuses and says to connect it under Destinations on the Publishing tab, whose Connect press is the provider's own sign-in. The Destinations card draws the state and no switch of its own: a destination is settled once and left alone, so the rare change is the sentence the writer says to Slug.

The ready-recording pair and the two media tools are Slug's own way through a feed that already exists: list what the project has finished and ready to add (list_publishing_ready_recordings), take one off that list for good (remove_publishing_ready_recording), write a release's description from what its recording performed (generate_publishing_release_description), and put the latest recording behind an already-published audio episode without moving its place, number or date in the feed (refresh_published_release_media).

Schedule, publish and cancel are outward-facing and take TWO CALLS. Called without confirmed, each answers a quote — the release, the series' destinations and feed URL, and a note ending "Nothing has been published." / "Nothing has been scheduled." / "Nothing has been canceled." — and changes nothing. Called again with confirmed: true after the writer has said so in their own words, it goes through the same core the schedule/publish/cancel routes call. A published release records publishedVia: user_confirmed with publishedByKeyId: null — the writer, in the app. None of these tools spends credits.

Slug can also list a project's connected apps (list_app_connections) on those same paid, project-bound conversations: connection metadata only — id, origins, sign-in page and method, which secrets are SAVED (kinds and dates) and what is still missing. A login is typed only on the project page's Connect an app form; the chat never asks for one and never repeats one, and no secret value ever leaves the core.

Workers

Publishing workers are queue-backed and idempotent. The release publish worker ships for scheduled/immediate publish: it completes the release's RSS target and dispatches every other target to that provider's registered publisher — YouTube and TikTok each register one in server/publishing-providers/ — so a new platform is one registration, with no worker change. Future workers can add cadence suggestions, provider status refresh, and analytics fetches.

Workers must refuse stale or live-preview-only assets. If the source artifact fingerprint changes after promotion, the release asset and targets are marked stale until the current durable artifact is promoted again.