---
title: "Add a trailer beat or a text card at a seam."
method: POST
path: "/trailers/{jobId}/storyboard/add-beat"
tags: ["Trailer"]
---

# Add a trailer beat or a text card at a seam.

`POST /trailers/{jobId}/storyboard/add-beat`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/trailers/{jobId}/storyboard/add-beat`
- Auth: HTTP bearer

Adds a new beat: `atIndex` is the position it takes, `afterBeatIndex` puts it just after that beat, and omitting both appends it to the end. NOT metered. With `text` ({ words, style? }) the beat is a TEXT CARD: its words in their style (absent: a statement), running as long as they take to read (2.5–6 s; words that cannot be read in 6 s are refused with the most the style carries); two text cards never sit side by side; a card between two shots stales neither; on a rendered trailer the master is stitched again with it, free (`masterRebuild: restitching`); the answer's `text` says how it plays. Without `text` the beat arrives blank, with no footage; direct it with `beats/refine`, then render it with `beats/render-clip` (priced). Returns `insertedBeatIndex`, the new `beatCount`, `staleBeats` and `captureOnly`. ON A TRAILER WITH NO SCREENPLAY (`captureOnly: true`) the beat is never rendered: make it a capture beat (`compositionKind: 'capture'` with a `captureId`) with `beats/refine`, or add a text card instead (`text`); approving or finishing the trailer is refused with `trailer_capture_only`, naming the beat, while it is blank. A trailer with no screenplay (POST /story-projects/{projectId}/trailers) is edited this way on its draft too, since no planner draws its beats; a screenplay trailer's draft keeps the plan its planner drew. Requires `Idempotency-Key`. Scope: `story:write`.

## Headers

- `Idempotency-Key` string, required

## Request body

- TrailerAddBeatRequest: Insert a new trailer beat at a seam: blank, or a text card (`text`). Give `atIndex` OR `afterBeatIndex`, or neither to append.
  - `atIndex` integer: Zero-based position the new beat takes.
  - `afterBeatIndex` integer: Insert just AFTER this existing beat.
  - `text` TextCardInsert: A new text card: its words, and its style (absent: a statement). It runs as long as the words take to read, 2.5–6 s. A text beat is words plus a style: statement (A theme or a title: one idea, centred, each word rising in out of a soft blur. At most 7 words.); thought (An interior line, a question or a note to self, low on the left, each line rising in. At most 14 words.); typed (Words being typed (a prompt, a search, a line the character writes), letter by letter with a blinking caret. At most 12 words.); reveal (An editorial title or a chapter name: a thin rule draws, then the line wipes on. At most 6 words.); label (Names what the product just did, beside the screen: small capitals on a dark pill. The only style a screen recording carries. At most 4 words.); stat (A number that matters ("3 hours back"): the number counts up, its words rise under it. At most 4 words.); directed (Anything else, moved the way plain words describe it (through the motion direction). At most 20 words.). Absent, a text card is a statement, words over picture a thought, and a screen recording's words a label. Tempo calm, standard or brisk; place centre, lower, upper, left or right; enterOn cut, a narrator word, or the score's downbeat; emphasis marks up to 2 exact words. A text change is free; a rendered cut is rebuilt free.
    - `words` string, required
    - `style` 'statement' | 'thought' | 'typed' | 'reveal' | 'label' | 'stat' | 'directed'

## Response `200`

The new beat's position + the beats now out of date.

- TrailerBeatOrderAck: The result of a beat-order edit. Costs no credits and renders nothing.
  - `success` boolean, required
  - `beatCount` integer, required: How many beats the cut has after the edit.
  - `staleBeats` integer[], required: Beats whose FOLLOWING beat changed, so their seam direction is out of date. Their footage stays playable.
  - `movedTo` integer: Where the moved beat ended up (move only).
  - `insertedBeatIndex` integer: Where the new beat landed (insert only).
  - `text` TrailerTextOutcome: How the beat's words will play once drawn by the master, and the cut's text density rules it breaks.
    - `style` 'statement' | 'thought' | 'typed' | 'reveal' | 'label' | 'stat' | 'directed' | 'null', nullable, required
    - `seconds` number, nullable, required: How long the beat runs (a text card: its read time).
    - `window` TrailerTextWordsWindow, required: When the words are on screen, in seconds from the beat's first frame: they start coming in, are fully in, start going, are gone.
      - `enterAt` number, required
      - `readFrom` number, required
      - `exitAt` number, required
      - `goneAt` number, required
    - `notes` string[], required: What the words could not keep as asked, and the density rules the cut breaks, in the writer's words.
  - `captureOnly` boolean: Insert only: the trailer has no screenplay, so the new beat is never rendered — make it a capture beat or a title card with `beats/refine`; approving or finishing it is refused with `trailer_capture_only` while it is blank.
  - `masterHold` TrailerMasterHold: On a rendered trailer with no screenplay, a blank beat holds its video: nothing rebuilds it until each blank beat is a capture beat or a title card, or removed (then it is rebuilt, free); the answer's `masterHold` names the beats and the fix, and the export is refused with it (`trailer_capture_only`).
    - `blankBeats` integer[], required: The blank beats, numbered from 1.
    - `message` string, required: The sentence to show: the beats, that the video keeps playing, and the fix.
  - `masterRebuild` 'reassembling' | 'restitching': Remove: the last blank beat of a rendered trailer with no screenplay was removed, so its held video is rebuilt (free): `reassembling` in review, `restitching` when finished. Insert of a text card on a rendered trailer: `restitching`, the master stitched again with it (free). Absent otherwise.

## Other responses

- `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.

## Changes

- **2026-10-20** `4534f5d25540`: 2 info
  - added the new optional request property `text`
  - added the optional property `text` to the response with the `200` status

[Operation history](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api/changes/trailers/:jobId/storyboard/add-beat/post.md)

---

[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)
