---
title: "Create a project around a script that belongs to no project."
method: POST
path: "/story-projects/from-script"
tags: ["Projects"]
---

# Create a project around a script that belongs to no project.

`POST /story-projects/from-script`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/story-projects/from-script`
- Auth: HTTP bearer

Wraps the script in a new project named after it, so its Series Bible, seasons, videos and publishing become reachable — the same path the Writers Lab uses. Pass exactly one of `scriptId` (the script id, or the id in its Writers Lab URL) or `scriptUploadId`. Idempotent by the script: a script already in a project returns that project with `created: false`. Once in a project, the script's Writers Lab pages open on the project Overview until its Series Bible is complete; `workspacePath` is that Overview. Costs no credits. Requires `Idempotency-Key` for safe retries.

## Headers

- `Idempotency-Key` string, required

## Request body

- StoryProjectFromScript: Exactly one of `scriptId` or `scriptUploadId`.
  - `scriptId` string: The script id, or the script id shown in its Writers Lab URL.
  - `scriptUploadId` string: Any version (script upload) of the script.

## Response `200`

The project that now holds the script.

- StoryProjectFromScriptEnvelope
  - `project` StoryProject, required
    - `id` string, required
    - `name` string, required
    - `description` string, nullable, required
    - `metadata` object, nullable, required
    - `kind` 'story' | 'product', required: What this project is: `story` (characters, seasons, episodes; judged on premise and series engine) or `product` (personas, features, truth; judged on the product Bible). It selects the coverage rubric and the Bible's shape, and once the project exists it is the project's own: it never follows the account. A create with no `kind` takes the account's role default: `product` for a Business account, `story` otherwise; the ChatGPT app always starts a `story`.
    - `workspaceRole` 'writer' | 'creator' | 'business', nullable, required: Every project is stamped with the workspace role it was made under (`writer`, `creator` or `business`): the creator's current role, or the role the create names. The sidebar's project list shows the current role's projects first, with one click to all of them. Null for a project made before roles were stamped, which shows under every role.
    - `seasonCadence` 'monthly', nullable, required: How a new episode is filed into a season. `monthly`: the season named for the month (UTC) its source material was made (`originatedAt` on the source, else when the source was added, else when the episode was made), created after the last season when the month is new. Null: numbered seasons (the season the plan, its season run or its Series Bible entry names, else Season 1). Stored as `metadata.seasonCadence`.
    - `medium` 'audio' | 'video', nullable, required: What the story is made as: `audio` (a podcast or audio drama: voices, music and sound, no picture) or `video` (for the screen). Read from the Series Bible's `productionMedium` (`audio` is audio, any visual medium is video); null until the Bible says.
    - `recurring` boolean, required: A recurring show: new episodes on a schedule from its sources. True when the project's show is made by Sleeper or by an outside producer under a standing approval.
    - `showSeriesId` string, nullable: The show's publishing series: the id `GET /publishing-series/{seriesId}/show` (MCP get_show, `sleeperhit show get`) takes. Null when the project is not a show.
    - `contentGuard` boolean, required: Keeps every script, plan and Series Bible written for the project suitable for ages 13 and up: no strong profanity, no graphic violence, no sexual content. On for a project started from ChatGPT; off otherwise unless set.
    - `brandLogoUrl` string, nullable, required: The project's brand logo on the studio's media host, or null. A project's brand logo is its own mark: a PNG, JPEG or WebP image of up to 2 MB and 4096 pixels on a side (a transparent PNG on a dark ground shows best), sent as `brandLogo` on the project update — `{ base64, contentType? }` with the file's bytes, or `{ sourceUrl }`, a public https address it is fetched from once. The server decodes it and stores its own clean copy on the studio's own media host (a PNG stays a PNG; a JPEG becomes a WebP), so a file that does not decode as the image it says it is is refused; `brandLogo: null` removes it. The project read returns its address as `brandLogoUrl`. A deck's or a trailer's closing sequence can end on it (`logoCard: true`): the closing's save copies the project's logo at that moment, so after replacing the logo, save the closing again to show the new one. Free.
    - `clickSound` object, nullable, required: The project's own click sound, or null for the stock click. A project's click sound is the product's own interface click, played on every press in every screen beat of the project's trailers instead of the studio's stock click: a sound from My Library (`librarySoundId`, that sound's id — import the product's own file there first), with `trimMs` (0–2000) skipping silence at its start and `gainDb` (-24 to +12) levelling it against the stock click once; each click plays at most 400 ms of it. Sent as `clickSound` on the project update, or null to go back to the stock click. A sound that is gone, or too short once trimmed, plays the stock click and the beat says why. Free; the next render of each trailer plays it.
      - `librarySoundId` string, required: The My Library sound it plays.
      - `label` string, required: The sound's name in My Library when it was picked.
      - `url` string, required: The sound's file on the studio's media host.
      - `trimMs` number, required: Milliseconds skipped at its start.
      - `gainDb` number, required: dB over each click's own level.
      - `durationMs` number, nullable, required: Its length, when My Library knows it.
    - `archivedAt` string, date-time, nullable, required
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
    - `sourceCount` integer
    - `yourRole` 'owner' | 'editor' | 'viewer': The caller's place on the project: `owner`, or `editor` / `viewer` on a product project shared with them (GET /story-projects/{projectId}/members). An editor changes the project's content but cannot delete it, archive it or manage its members; a viewer reads.
    - `show` ProjectListShow
      - `seriesId` string, required: The show's publishing series (`showSeriesId`).
      - `state` 'not_set_up' | 'off' | 'on' | 'paused' | 'external', required: The show's state: `on`, `off`, `paused`, `not_set_up`, or `external` (an outside app makes it).
      - `timezone` string, required: The show's clock (IANA), for its days and times.
      - `publishedEpisodes` integer, required: Episodes published in the show's feed (episodes only, never trailers or extras).
      - `nextRunAt` string, date-time, nullable, required: When the next scheduled run makes episodes. Null when the show is not on or nothing is scheduled.
      - `next` string, nullable, required: `nextRunAt` in words, in the show's clock: "tonight 7 pm Central".
      - `latestEpisode` object, nullable, required: The newest published episode, or null before the first.
        - `releaseId` string, required: The feed release (GET /publishing-releases/{releaseId}).
        - `scriptId` string, nullable, required: The script the episode was made from, when the release names one.
        - `href` string, nullable, required: The episode's page in the app (a path on the studio's site), when it has a script.
        - `code` string, nullable, required: Its number: "S4E429" in a season, "Ep 12" without one.
        - `title` string, required
        - `durationMs` integer, nullable, required: Its audio length, when known.
        - `publishedAt` string, date-time, required
  - `scriptId` string, required: The wrapped script.
  - `created` boolean, required: True when this call made a new project; false when the script was already in one, or rejoined the live project that already wrapped it.
  - `workspacePath` string, required: The project Overview in the Writers Lab, relative to the site origin.

## 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 named project or source does not exist 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)
