---
title: "Start a destination video deliverable."
method: POST
path: "/story-projects/{projectId}/deliverables"
tags: ["Projects"]
---

# Start a destination video deliverable.

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

## Request

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

Start a destination video deliverable: `title` names it and `primaryDestinationKey` is where the finished video goes — resolved through the video destinations, so an unknown key is 404 `video_destination_not_found` and a row whose spec fails its check is 409 `video_destination_unavailable`, never guessed. The PRIMARY variant (the master cut) is created with it.

## Request body

- VideoDeliverableCreateRequest: Start a destination video deliverable. `primaryDestinationKey` is the destination the finished video goes to; the PRIMARY variant (the master cut) is created with the deliverable.
  - `title` string, required: Names the deliverable.
  - `primaryDestinationKey` string, required: A video destination key, as listed in `destinations[].key`. An unknown key is 404 `video_destination_not_found`; a row whose spec fails its check is 409 `video_destination_unavailable` — never guessed.

## Response `200`

The deliverable, its PRIMARY variant and its resolved primary destination.

- VideoDeliverableEnvelope: The deliverable with its variants, its resolved primary destination (or why it cannot be used), and the ordered case cards when the destination asks for them.
  - `deliverable` VideoDeliverable, required: One destination video deliverable: the title, the destination its PRIMARY variant is made for, its OWN surface scout, its OWN imported test-case script, the per-step state, and every variant.
    - `id` string, required
    - `projectId` string, required
    - `userId` string, required
    - `title` string, required
    - `primaryDestinationKey` string, required: The VideoDestination.key the PRIMARY variant is made for.
    - `cutTrailerJobId` string, nullable: The one editable cut's trailer job, or null.
    - `scout` object, nullable: Ranked surface moments, in rank order: `{ id, tool, kind, why, evidence[], legibilityEstimate, setupNeeded, risk }` per moment. Validated against the shared `surfaceScoutSchema`; an unknown moment kind or any other malformed field is a named 400 and nothing is stored.
      - `moments` object[], required
        - `id` string, required: Snake-case moment id (`^[a-z][a-z0-9_]{0,63}$`, at most 64 chars), unique within the document — a duplicate id is refused with the same named 400.
        - `tool` string, required: The tool this moment films. Trimmed by the validator: it must contain a non-whitespace character.
        - `kind` 'widget_render' | 'approval_prompt' | 'quote' | 'list' | 'refusal', required: What filming this moment shows: `widget_render`, `approval_prompt`, `quote`, `list` or `refusal`.
        - `why` string, required: Why the moment earns its rank. Trimmed by the validator: it must contain a non-whitespace character.
        - `evidence` object[], required
          - `kind` 'tool_annotation' | 'output_template_present' | 'route' | 'checkpoint', required
          - `detail` string, required: The receipt a reviewer (or the cut) can check the moment against. Trimmed by the validator: it must contain a non-whitespace character.
        - `legibilityEstimate` string, required: The human estimate of on-screen legibility. Trimmed by the validator: it must contain a non-whitespace character.
        - `setupNeeded` boolean, required
        - `risk` object, required
          - `spend` boolean, required
          - `destructive` boolean, required
    - `script` DestinationScript, required: A deliverable's own test-case script: the cases, and the starter prompts and release notes the packet is built around.
      - `cases` DestinationScriptCase[], required
        - `id` string, required: `P1`…`P5` for a positive case, `N1`…`N3` for a negative one.
        - `polarity` 'positive' | 'negative', required
        - `scenario` string, required: The first sentence of what the case shows.
        - `user_prompt` string, required
        - `tools_triggered` string[], required
        - `expected_output` string, required: The whole expected result (positive) or expected behaviour (negative).
        - `platform` 'web' | 'mobile_app' | 'mobile_web', required
        - `setup` string, required
        - `mustShowMoment` string, required
      - `starterPrompts` string[], required
      - `releaseNotes` string, required
    - `scriptVersion` integer, required: How many imports the script has had (0 = none). A variant's `lastCheck.scriptVersion` names the one it checked.
    - `steps` object, required: Per step: `{ state, jobId, inputHash, revision, error, updatedAt }`.
    - `revision` integer, required
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
    - `variants` VideoDeliverableVariant[], required
      - `id` string, required
      - `deliverableId` string, required
      - `destinationKey` string, required: The video destination key this variant is cut and checked for.
      - `trailerJobId` string, nullable: The trailer job holding this variant's cut, or null before one is cut.
      - `exportTarget` string, nullable: The export target key (`site` | `youtube` | `x`), or null.
      - `lastCheck` object, nullable: The kept check report `{ masterSha256, cutRevision, scriptVersion, specVersion, results[], checkedAt }`, or null when the variant has not been checked since the last script import (every import clears it).
      - `shareUrl` string, nullable
      - `status` 'draft' | 'cutting' | 'checking' | 'ready' | 'failed', required
      - `createdAt` string, date-time, required
      - `updatedAt` string, date-time, required
  - `destination` VideoDestination, required
    - `key` string, required: Stable key, e.g. `chatgpt_app_review`.
    - `name` string, required
    - `family` 'plugin_review' | 'marketing' | 'social' | 'site', required
    - `description` string, required
    - `spec` object, required: What the destination asks of the video, checked on every read (a row that fails is never listed here): `content` (test cases, platforms, moments it must show, how waits are cut, which moments it draws on), `picture` (aspect, delivery, length, depth preset), `sound` (narration, music, loudness target, captions), `host` (the end system a review walkthrough is recorded in: origins, the lanes it may be recorded through, masks, controls named by role), `delivery` (link), `exports` (mp4, packet, stills) and `checks` (each a named kind with its params and `block` or `warn`), and `intent` (purpose and audience).
    - `guidanceSnippet` string, nullable, required: The Snippet whose guidance applies to this destination's videos.
    - `sourceUrls` string[], required: Where the destination's own requirements were read.
    - `verifiedAt` string, date-time, nullable, required: When the spec was last checked against `sourceUrls`.
    - `specMayBeStale` boolean, required: True when `verifiedAt` is more than 90 days old, or the row names `sourceUrls` and was never checked.
    - `specVersion` integer, required
    - `sortOrder` integer, required
  - `destinationUnavailable` object, nullable, required: Why the primary destination cannot be used right now, or null when it can. The deliverable stays readable either way.
    - `code` 'video_destination_not_found' | 'video_destination_unavailable', required
    - `reason` string, required
  - `scout` object, nullable: Ranked surface moments, in rank order: `{ id, tool, kind, why, evidence[], legibilityEstimate, setupNeeded, risk }` per moment. Validated against the shared `surfaceScoutSchema`; an unknown moment kind or any other malformed field is a named 400 and nothing is stored.
    - `moments` object[], required
      - `id` string, required: Snake-case moment id (`^[a-z][a-z0-9_]{0,63}$`, at most 64 chars), unique within the document — a duplicate id is refused with the same named 400.
      - `tool` string, required: The tool this moment films. Trimmed by the validator: it must contain a non-whitespace character.
      - `kind` 'widget_render' | 'approval_prompt' | 'quote' | 'list' | 'refusal', required: What filming this moment shows: `widget_render`, `approval_prompt`, `quote`, `list` or `refusal`.
      - `why` string, required: Why the moment earns its rank. Trimmed by the validator: it must contain a non-whitespace character.
      - `evidence` object[], required
        - `kind` 'tool_annotation' | 'output_template_present' | 'route' | 'checkpoint', required
        - `detail` string, required: The receipt a reviewer (or the cut) can check the moment against. Trimmed by the validator: it must contain a non-whitespace character.
      - `legibilityEstimate` string, required: The human estimate of on-screen legibility. Trimmed by the validator: it must contain a non-whitespace character.
      - `setupNeeded` boolean, required
      - `risk` object, required
        - `spend` boolean, required
        - `destructive` boolean, required
  - `caseCards` CaseCard[]: One title card per script case, in script order, when the destination asks for case cards (`content.caseCard`); [] when it does not, or when no script has been imported.
    - `kind` 'title', required
    - `caseId` string, required: Which test case this card opens.
    - `label` string, required: What the card shows — the "<caseId> · <user_prompt>" text capped for the screen.
    - `captionText` string, required: The words on the screen — the same capped text as `label`, never the full prompt.
    - `compositionKind` 'text', required
    - `durationSeconds` number, required: The card hold, in seconds.

## 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 primary destination's spec fails its check (`video_destination_unavailable`, `details.reason`).
- `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/64c1102ccb2f?raw)
