---
title: "Start an async Director compose workflow"
method: POST
path: "/v1/projects/{id}/compose"
tags: ["compose"]
---

# Start an async Director compose workflow

`POST /v1/projects/{id}/compose`

## Request

- Base URL: `https://api.cueframe.ai`
- URL: `https://api.cueframe.ai/v1/projects/{id}/compose`
- 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:write`)
  - API key in header `PAYMENT-SIGNATURE`

Async sibling of `composeFromSuggestion`. The Director execution authors a canonical wire-shape Composition and saves it as the project's active composition. Watch progress via the SSE stream at `/v1/projects/:id/compose/:composeJobId/stream`.

## Path parameters

- `id` string, required

## Request body

- ComposeCreateRequest: POST /projects/:id/compose body.
  - `suggestionId` string
  - `fromComposition` boolean: Reuse the project's current composition as the compose starting point (critique-retry path). The Director RE-AUTHORS graphics: if the current composition is client-authored and contains graphic clips (overlay/component/card), the request is refused (validation_error naming the clips) — render it verbatim with create_render instead. The director's own output (critique-retry) is exempt.
  - `briefId` string: Compose from a persisted Brief (create_brief) — the intent-first door: every Brief field is HONORED or the request is refused loudly naming the field (window from beat moment-pinning; seeded graphics carried VERBATIM as locked clips; exclusions enforced; locale + caption emphasis threaded to the Director). Exactly ONE of suggestionId | fromComposition | briefId.
  - `format` Format
    - `aspectRatio` '16:9' | '9:16' | '1:1' | '4:5', required
    - `fps` union
      - 24
      - 25
      - 30
      - 60
    - `resolution` 'hd' | 'fhd' | '4k'
    - `platform` string
  - `scope` unknown
  - `critique` string: Structured critique block (per compose-api-contract.md decision #11). Capped at 2KB total; per-criterion blocks capped at 500 chars.
  - `parentJobId` string: Pointer to the prior compose job this run iterates on. Surfaced on the resulting compose job as baselineComposeJobId.
  - `maxCandidates` integer: Ensemble size, 1–5. Defaults to one; explicit multi-candidate runs divide the job budget across candidates and admit at most two concurrently. A held editorial checkpoint always uses one candidate.
  - `budgetUsd` number: Per-job budget cap (USD). Clamped at the route against the org's composeMaxBudgetUsdPerJob setting.
  - `qualityThreshold` number: Composite-score accept threshold. Server default per contract.
  - `seed` integer: Deterministic seed for replay.
  - `clientNonce` string: Opaque caller token that disambiguates intentional same-critique retries. Without it, identical (project, sug, seed, critique) tuples deduplicate; with it, retries create a fresh execution.
  - `holdAt` string[]: Opt-in checkpoint gates. Each named stage pauses the compose at its boundary (status → awaiting_input) for client review via resume. Absent/empty ⇒ fully autonomous. Non-empty ⇒ the run is a single spine.

## Response `202`

Compose job queued.

- ComposeCreateResponse: POST /projects/:id/compose response.
  - `composeJobId` string, required: Opaque compose job id; pass to the SSE stream URL.
  - `projectId` string, required
  - `status` 'queued' | 'failed', required: queued = normal; failed = startup failed and the job is already terminal (the SSE stream will emit a single `job_failed` frame and close).
  - `reconciliationRequired` boolean
  - `streamUrl` string: Relative SSE endpoint to stream this job's events (per compose-api-contract.md).

## Other responses

- `400`: Bad request — validation_error / invalid_cursor / invalid_query.
- `401`: Missing or invalid Authorization header.
- `402`: Payment required — billing_required or x402 payment_*.
- `403`: Forbidden — permission insufficient.
- `404`: Resource not found.
- `422`: Unprocessable — invalid_composition / media_in_use / session_in_use / render_not_cancellable.
- `429`: Rate limited. Retry-After header present.
- `500`: Internal error.
- `503`: Service unavailable — billing_unavailable / honest-503 for runtime deps.

---

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