---
title: "Make a trailer from the project's app captures alone, with no screenplay (free)."
method: POST
path: "/story-projects/{projectId}/trailers"
tags: ["Trailer"]
---

# Make a trailer from the project's app captures alone, with no screenplay (free).

`POST /story-projects/{projectId}/trailers`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/story-projects/{projectId}/trailers`
- Auth: HTTP bearer

A capture-only trailer is a NEW `draft` trailer for the whole project with no screenplay behind it: its beats play the project's app captures exactly as recorded (and title cards), never filmed footage. Creating it is free. `captureId` seeds beat 1 with a ready capture, the whole of it or the stretch `captureKeep` keeps (a capture longer than 30 seconds opens on a kept stretch of 4 to 30 seconds); without one, beat 1 is a title card with the trailer's title. Add a beat per capture (insert a beat, then refine it with `compositionKind: 'capture'` and the `captureId`). Approving the plan needs at least one capture beat and renders nothing: it assembles the cut from the captures, and only a title card's text layer is ever priced. A beat that would be filmed is refused (`trailer_capture_only`): there is no screenplay to film from. `captureOnly: true` is required (a planned trailer is made from a story plan). `captureId` is checked before anything is stored, with the stretch `captureKeep` keeps of it when one is sent (a capture longer than 30 seconds opens on a kept stretch of 4 to 30 seconds; `captureKeep` without `captureId` is 400): a capture that is not the project's, not ready, a raw master, playing outside 4 to 30 seconds, a kept stretch that does not fit it, or in another shape than `aspectRatio` is refused with 409 `trailer_state_invalid` (`details.refusal.code` names the problem: `trailer_capture_raw`, `trailer_capture_length`, `trailer_capture_keep_range`, `trailer_capture_keep_cuts_result`, `trailer_capture_shape`, `trailer_capture_not_ready`, `trailer_capture_rejected`) or 404 `capture_not_found`, and no trailer is made. NOT metered and NOT confirmed. Requires `Idempotency-Key`. Scope: `story:write`. Only a product project whose workspace gate is open makes one, as the Videos page offers it only then: a story project is refused with 409 `project_not_product`, and a product project whose product Bible is not complete or whose product coverage has not passed with 409 `project_precondition_failed` (`details.stage` names the stage), before anything is stored.

## Headers

- `Idempotency-Key` string, required

## Request body

- CaptureOnlyTrailerRequest: A capture-only trailer is a NEW `draft` trailer for the whole project with no screenplay behind it: its beats play the project's app captures exactly as recorded (and title cards), never filmed footage. Creating it is free. `captureId` seeds beat 1 with a ready capture, the whole of it or the stretch `captureKeep` keeps (a capture longer than 30 seconds opens on a kept stretch of 4 to 30 seconds); without one, beat 1 is a title card with the trailer's title. Add a beat per capture (insert a beat, then refine it with `compositionKind: 'capture'` and the `captureId`). Approving the plan needs at least one capture beat and renders nothing: it assembles the cut from the captures, and only a title card's text layer is ever priced. A beat that would be filmed is refused (`trailer_capture_only`): there is no screenplay to film from.
  - `captureOnly` true, required: Always true: the trailer plays the project's app captures alone. A planned trailer is made from a story plan.
  - `title` string, required: The trailer's name.
  - `aspectRatio` '16:9' | '9:16' | '1:1': The shape it plays at. Every capture it plays must have this shape.
  - `captureId` string: A ready app capture of the project (an edit, never a raw master; playing 4 to 30 seconds — the whole capture, or the stretch `captureKeep` keeps; in `aspectRatio`) to play as beat 1. Omitted: beat 1 is a title card with the title.
  - `captureKeep` object: The stretch of `captureId` beat 1 plays (sent only with it; omitted: the whole capture). A capture longer than 30 seconds opens on a kept stretch. The stretch of its capture a capture beat plays: `captureKeep` with `startSeconds` and `endSeconds` (seconds of the capture, decimals allowed). The kept stretch must start before it ends, end inside the capture, and run 4 to 30 seconds; the beat then runs that long, and its sound, cursor and camera follow the stretch. Null plays the whole capture again. Changing the beat's capture drops the kept stretch unless you send one with it. Free. A stretch that does not fit the capture is refused (409, `trailer_capture_keep_range`); so is a stretch that ends before the result of a click it keeps (409, `trailer_capture_keep_cuts_result`, with the sentence naming the end that would keep it); and no trailer is made.
    - `startSeconds` number, required: Where the kept stretch starts, in seconds of the capture.
    - `endSeconds` number, required: Where it ends, in seconds of the capture: after the start, inside the capture.

## Response `200`

The new draft trailer.

- CaptureOnlyTrailer: A capture-only trailer is a NEW `draft` trailer for the whole project with no screenplay behind it: its beats play the project's app captures exactly as recorded (and title cards), never filmed footage. Creating it is free. `captureId` seeds beat 1 with a ready capture, the whole of it or the stretch `captureKeep` keeps (a capture longer than 30 seconds opens on a kept stretch of 4 to 30 seconds); without one, beat 1 is a title card with the trailer's title. Add a beat per capture (insert a beat, then refine it with `compositionKind: 'capture'` and the `captureId`). Approving the plan needs at least one capture beat and renders nothing: it assembles the cut from the captures, and only a title card's text layer is ever priced. A beat that would be filmed is refused (`trailer_capture_only`): there is no screenplay to film from.
  - `jobId` string, required: The NEW draft trailer. Every trailer tool takes this id from here on.
  - `projectId` string, required
  - `status` 'draft', required
  - `title` string, required: The trailer's name.
  - `aspectRatio` '16:9' | '9:16' | '1:1', required
  - `audioPolicy` string, required: `narrate` (the default): a beat's narration line is spoken once a narrator is chosen.
  - `beats` integer, required: How many beats it starts with.
  - `captureBeats` integer, required: How many of them play an app capture.
  - `planCoverage` 'not_needed', required: A trailer with no screenplay is never scored by plan coverage; its cut is reviewed once it is made.
  - `nextStep` 'approve_trailer_plan', required
  - `reviewUrl` string, nullable, required: A logged-in deep link that opens the trailer on the project's Videos page.
  - `conversationId` string, nullable, required: The Slug conversation that governs the trailer. It is made with its Slug conversation already bound (`conversationId`): Slug inserts, refines and approves its beats in that conversation without the Videos page being opened first. A workspace whose plan does not include a video's conversation gets none (`conversationId` null): the video is edited on the Videos page, the Story API, MCP or the CLI.
  - `credits` 0, required: Always 0: making it renders and reserves nothing.
  - `note` string, required: What was made and what happens next, in one paragraph to read out.

## 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.
- `409`: 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 (`trailer_state_invalid`), and nothing was reserved: a plan approved outside draft, a take chosen or a beat rendered outside review/complete, an export before the finish, a finish while a call has no selected take, a narrated cut with no narrator, a render the compile blocked, or a finish while the master failed its video coverage (`trailer_video_coverage_failed`, with `details.videoCoverage`). `details.refusal` is `{ code, action, fix }` (see TrailerRefusalDetails): `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; `details.blocked[]` lists every beat a blocked compile could not render. The identical request succeeds once the trailer is put right.
- `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)
