---
title: "Get a brief by id"
method: GET
path: "/v1/briefs/{id}"
tags: ["briefs"]
---

# Get a brief by id

`GET /v1/briefs/{id}`

## Request

- Base URL: `https://api.cueframe.ai`
- URL: `https://api.cueframe.ai/v1/briefs/{id}`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.cueframe.ai/api/auth/mcp/authorize`, token URL `https://api.cueframe.ai/api/auth/mcp/token`, scopes: `projects:read`)
  - API key in header `PAYMENT-SIGNATURE`

Required permission: projects:read

## Path parameters

- `id` string, required

## Response `200`

The brief (with its quote).

- BriefResponse
  - `id` string, required
  - `projectId` string, required
  - `kind` 'authored' | 'clip', required: authored = create_brief; clip = minted by suggest_briefs from an AI clip suggestion.
  - `sourceSuggestionId` string: kind:'clip' provenance — the suggestion this Brief was minted from.
  - `name` string
  - `goal` string, required: What the video must accomplish.
  - `audience` string
  - `platform` string
  - `format` Format
    - `aspectRatio` '16:9' | '9:16' | '1:1' | '4:5', required
    - `fps` union
      - 24
      - 25
      - 30
      - 60
    - `resolution` 'hd' | 'fhd' | '4k'
    - `platform` string
  - `durationSec` number: Target output duration. HONOR-OR-REFUSE: compose validates it against the beat-source window (±1.5s) and refuses a brief it cannot honor.
  - `tone` string
  - `cta` string
  - `locale` string: Output language (BCP-47 tag, e.g. 'es-419'): ALL on-screen copy + captions.
  - `hook` string
  - `caption` string
  - `beats` BriefBeat[]
    - `id` string, required
    - `intent` string, required: What this beat DOES editorially (hook the claim, prove it, CTA…).
    - `durationSec` number
    - `source` object: MOMENT PINNING: this beat uses EXACTLY this window of this media item — honored to-the-frame or refused. v1: every sourced beat must reference the SAME mediaId (multi-source briefs are refused loudly, not silently merged).
      - `mediaId` string, required
      - `startSec` number, required
      - `endSec` number, required
    - `graphic` object: GRAPHICS MANDATE: the plan MUST carry a graphics request of this role overlapping this beat's window — enforced at the plan gate (honor-or-refuse, like moment pinning). Prose mustIncludes reaches only the judge; this is the checkable form.
      - `role` 'title-card' | 'stat' | 'lower-third' | 'pull-quote' | 'hero' | 'logo-tag' | 'enumeration' | 'sequence' | 'comparison', required: The mandated graphic's editorial role (the graphics-plan GraphicRole vocabulary).
      - `items` string[]: Mandated list items (enumeration/sequence) — carried VERBATIM into the plan's copy.items.
  - `mustIncludes` string[]
  - `references` string[]
  - `captions` object
    - `styleId` string: Caption style id (e.g. 'serif-emphasis@1').
    - `emphasisPhrases` string[]: Phrases rendered in the caption EMPHASIS style (the big-serif treatment). Matched against the transcript; drives CaptionWord.emphasis.
  - `motionStyle` object
    - `textAnimation` string
    - `transitions` string
    - `colorMood` string
    - `captionStyle` string
  - `seededGraphics` TimelineClip[]: Client-authored graphic clips the Director must carry VERBATIM: seeded into the design loop's base composition as LOCKED clips (never re-authored, never discarded).
    - `id` string, required
    - `startTime` number, required
    - `duration` number, required
    - `source` union, required
      - object
        - `kind` 'media', required
        - `mediaId` string, required
        - `trim` object
          - `start` number, required
          - `end` number, required
        - `excludedRanges` object[]
          - `start` number, required
          - `end` number, required
          - `reason` string
        - `volume` number
        - `muted` boolean
        - `playbackRate` number
        - `sync` 'video' | 'independent'
        - `kenBurns` 'in' | 'out' | 'pan-l' | 'pan-r' | 'none'
        - `volumeDb` number
        - `fadeInMs` number
        - `fadeOutMs` number
        - `loop` boolean
        - `audioRole` 'sfx' | 'music' | 'vo' | 'ambience'
        - `duck` object
          - `duckDb` number, required
          - `attackMs` number, required
          - `releaseMs` number, required
          - `windows` object[]
            - `startMs` number, required
            - `endMs` number, required
        - `anchor` object
          - `clipId` string, required
          - `offsetMs` number, required
        - `reframe` object
          - `segments` object[], required
            - `startSec` number, required: Segment START in seconds, clip-relative. Segments tile the clip's duration in order.
            - `endSec` number, required: Segment END in seconds, clip-relative. The span (endSec − startSec) is this segment's duration — the window `ease` ramps over.
            - `focus` union, required: WHO/WHAT the camera tracks this segment (the reframe center), selected by `mode`: `frame-center` = no tracking, hold the geometric center (static/graphic footage, or a pure zoom); `point` = lock to a fixed source-normalized {x,y} (a non-moving on-screen element); `face` = track a detected face by `faceId` (from get_media_context), with optional `shotScale` close|standard|wide for tightness; `all-faces` = frame the bounding box of every detected face (group/ensemble shots); `active-speaker` = follow whoever is speaking (diarized talking-head / podcast). Pick by what the beat is about; pair with `zoom` for how tight.
              - …
            - `intent` string: Optional editorial LABEL for diff/log readability (e.g. 'speaker-tight', 'wide-context'). Cosmetic — NOT honored by the derive math and excluded from the variant cache key.
            - `zoom` number: How TIGHT the framing is — the fraction of the source the output viewport shows. <1 punches IN (a smaller source fraction, magnified to fill the frame); 1 = the full frame (no crop). ~0.6–0.8 is a moderate push-in, ~0.5 a tight close-up, 0.1 the maximum zoom. There is NO zoom-OUT past the full frame (max 1). Omit for the full frame. MUTUALLY EXCLUSIVE with `focus.shotScale`: a face-tracked shot is sized EITHER by the editorial scale (shotScale, calibrated to the detected face) OR by an explicit zoom — never both.
            - `ease` object: Zoom/center ramp DURATIONS in SECONDS at this segment's edges — NOT a unitless 0–1 curve. ~0.3–0.6s reads as a snappy push; >1s reads sluggish. Keep `in` + `out` ≤ the segment's duration (endSec − startSec): the two edges share the span, so to ramp across the WHOLE segment set one edge to the full duration and the other to 0. Omit for an instant cut to this framing.
              - …
            - `bias` object: Optional FINE framing nudge off the focus center, source-normalized in [−0.3, 0.3]. A small correction (headroom, rule-of-thirds), NOT a reframe — use `focus`/`zoom` for the actual shot. Omit (treated as 0) to sit dead-center on the focus.
              - …
      - object
        - `kind` 'component', required
        - `componentId` string, required
        - `props` object, required
        - `zPlane` 'front' | 'behind-subject'
        - `execution` union
          - object
            - `version` 3, required
            - `componentId` string, required
            - `sourceVersionId` string, required
            - `params` object, required
            - `seed` string, required
            - `assets` object
            - `mediaBindings` object
          - object
            - `version` 4, required
            - `componentId` string, required
            - `sourceVersionId` string, required
            - `params` object, required
            - `seed` string, required
            - `assets` object
            - `mediaBindings` object
        - `placement` union
          - object
            - `version` 3, required
            - `kind` 'clip', required
          - object
            - `version` 3, required
            - `kind` 'content-slots', required
            - `slots` object, required
        - `retired` object
      - object
        - `kind` 'overlay', required
        - `primitiveId` string, required
        - `params` object, required
        - `zPlane` 'front' | 'behind-subject'
        - `anchor` object
          - `space` 'scene', required
          - `x` number, required
          - `y` number, required
        - `parallax` number
      - object
        - `kind` 'effect', required
        - `component` union
          - object
            - `version` 3, required
            - `componentId` string, required
            - `sourceVersionId` string, required
            - `params` object, required
            - `seed` string, required
            - `assets` object
            - `mediaBindings` object
          - object
            - `version` 4, required
            - `componentId` string, required
            - `sourceVersionId` string, required
            - `params` object, required
            - `seed` string, required
            - `assets` object
            - `mediaBindings` object
        - `targetTrackIds` string[]
        - `wrapperOrder` integer
        - `primitiveId` string
        - `params` object
      - object
        - `kind` 'card', required
        - `html` string, required
        - `tokens` object
        - `zPlane` 'front'
      - object
        - `kind` 'composition', required
        - `compositionId` string, required
        - `trim` object
          - `start` number, required
          - `end` number, required
    - `kind` 'video' | 'audio' | 'image' | 'overlay' | 'effect'
    - `opacity` number
    - `scale` number
    - `rotation` number
    - `position` object: Clip CENTRE as a fraction of the frame: {x:0.5,y:0.5} is dead centre, 0 to 1 is inside the frame, and a little overshoot (down to -1, up to 2) is how an entrance starts off-canvas. NOT pixels: the renderer maps x to `left: x*100%`, so `x: 180` sits 180 frame-widths away and draws nothing.
      - `x` number, required
      - `y` number, required
    - `region` object
      - `x` number, required
      - `y` number, required
      - `w` number, required
      - `h` number, required
    - `fit` 'cover' | 'contain'
    - `captionVisibility` 'visible' | 'hidden': Video media clip only. Hide native captions while this clip is topmost and covers the lower picture, or when an opaque, effect-free clip has an explicitly full-frame region, even with fit:contain. Caption words and timings remain stored and resume when another visible clip takes over.
    - `effects` object[]
      - `id` string
      - `component` union
        - object
          - `version` 3, required
          - `componentId` string, required
          - `sourceVersionId` string, required
          - `params` object, required
          - `seed` string, required
          - `assets` object
          - `mediaBindings` object
        - object
          - `version` 4, required
          - `componentId` string, required
          - `sourceVersionId` string, required
          - `params` object, required
          - `seed` string, required
          - `assets` object
          - `mediaBindings` object
      - `window` object
        - `startTime` number, required
        - `duration` number, required
      - `primitiveId` string
      - `params` object
    - `transitionIn` object
      - `type` string, required
      - `duration` number, required
      - `id` string
      - `component` union
        - object
          - `version` 3, required
          - `componentId` string, required
          - `sourceVersionId` string, required
          - `params` object, required
          - `seed` string, required
          - `assets` object
          - `mediaBindings` object
        - object
          - `version` 4, required
          - `componentId` string, required
          - `sourceVersionId` string, required
          - `params` object, required
          - `seed` string, required
          - `assets` object
          - `mediaBindings` object
      - `fromClipId` string
      - `toClipId` string
      - `handlePolicy` 'outgoing-overrun-at-cut-v1'
    - `linkGroupId` string
    - `tweens` object[]
      - `id` string, required
      - `propertyGroup` 'visual' | 'scale' | 'rotation' | 'position', required
      - `position` number, required
      - `duration` number, required
      - `keyframes` object, required
        - `format` 'percentage', required
        - `easeEach` 'none', required
        - `keyframes` object[], required
          - `percentage` number, required
          - `properties` object, required
          - `ease` string
    - `automation` object
      - `version` 1, required
      - `lanes` object[], required
        - `target` 'volume', required
        - `points` object[], required
          - `t` number, required
          - `v` number, required
    - `legacyKeyframes` object
      - `needsUpgrade` true, required
      - `channels` string[], required
      - `record` unknown, required
  - `exclusions` BriefExclusion[]
  - `gates` BriefGates
    - `holdAt` string[]: Checkpoint gates — the compose pauses at each named stage for review via resume. DEFAULT (gates absent): the compose holds at EVERY stage — your agent reviews and resumes each one; an unreviewed checkpoint times out loudly after 60min. Auto-progression is the opt-in (gates.autoApprove: true).
    - `autoApprove` boolean: true = fully autonomous (compose runs with NO gates — the headless opt-in; without it a brief holds at every stage by default). Mutually exclusive with a non-empty holdAt.
    - `reviewMode` 'agent' | 'hosted': Who captures the review decision. Default 'agent': you relay the packet and call resume. 'hosted': every checkpoint mints a review-page URL (checkpoint.ready payload + get_checkpoint's reviewUrl) where a human approves/revises from the browser — no account, no agent session; the recorded decision rides the SAME resume rail and you learn of it via checkpoint webhooks / wait_job.
  - `audio` BriefAudio
    - `music` object
      - `enabled` boolean, required: Whether the compose carries a music bed.
      - `mood` string: Freeform mood/genre prompt for the GENERATED bed (e.g. 'understated editorial, warm'). Ignored when mediaId pins a track.
      - `mediaId` string: Pin a client-imported track as the bed. HONOR-OR-REFUSE: must be an org-owned kind:'audio' media item, else 422 — never accept-and-ignore. Absent ⇒ generate from mood.
    - `sfx` object
      - `intensity` 'off' | 'light' | 'standard' | 'dense', required: SFX placement density. Real-footage content keeps restraint caps regardless ('dense' is the explicit opt-out); generated/graphic-led content unlocks the standard tier.
  - `quote` BriefQuote, required
    - `composeUsd` number, required
    - `renderUsdPerExport` number, required
    - `estimatedJourneyUsd` number, required: compose + expected renders — relay this BEFORE composing.
    - `revisionCyclesIncluded` integer, required: Revise cycles the compose price includes per gate (A1). Beyond this, resume(revise) is refused with `revision_cycles_exhausted` — saying no never silently charges.
  - `createdAt` number, required
  - `updatedAt` number, required

## Other responses

- `400`: Bad request — validation_error / invalid_cursor / invalid_query.
- `401`: Missing or invalid Authorization header.
- `402`: Payment required. Returned when the caller presents no API key on a priced operation: the body AND the `Payment-Required` header carry the x402 v2 challenge (accepted scheme, network, USDC asset, amount, payTo). Sign the EIP-3009 authorization and retry with a `PAYMENT-SIGNATURE` header. A KEYED caller sees this status instead when their plan lacks or has exhausted the feature (`billing_required`).
- `403`: Forbidden — permission insufficient.
- `404`: Resource not found.
- `500`: Internal error.

---

[API](https://skmtc.dev/cueframe/apis/cueframe-api.md) · [All operations](https://skmtc.dev/cueframe/apis/cueframe-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cueframe/apis/cueframe-api/revisions/8f9bfe362b4a?raw)
