> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uselamina.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Compose Narrated Video

> Compose a **narrated, multi-shot** video from a script — not a single
~5s clip. Synthesizes the narration, storyboards it into beats, generates
a continuity-chained shot per beat, and renders with transitions +
Ken-Burns + captions.

**Asynchronous** — composition takes minutes. You get a `runId`
immediately; poll `GET /v1/compose/video/{runId}` (the snapshot carries
stage `progress`) or use it after `POST /v1/compose/plan` to confirm cost.

**Steer the render via the script, never models:** optional `direction`
(free text) and `sections` (per-span hints). Pass `idempotencyKey` to make
retries safe (a repeated key returns the same run — no double charge).


Compose a **narrated, multi-shot** video from a script — not a single \~5s clip. Lamina synthesizes the narration, storyboards it into beats, generates a continuity-chained shot per beat, and renders with transitions, Ken-Burns, and captions.

The operation is **asynchronous** — composition takes minutes. You get a `runId` immediately; poll [Get Compose Run](/api-reference/get-compose-run) for stage `progress` and the final video URL.

**Steer the render through the script, never models:** optional `direction` (free text) and `sections` (per-span hints). Pass an `idempotencyKey` to make retries safe — a repeated key returns the same run, so you never double-charge. Invalid input returns a `400` whose `details.reason` is machine-readable (`SCRIPT_TOO_LONG`, `MISSING_REQUIRED_INPUT`, …) so an agent can self-correct.


## OpenAPI

````yaml POST /v1/compose/video
openapi: 3.1.0
info:
  title: Lamina Apps API
  version: 1.0.0
  description: >
    Professional creative content production for AI agents. Create brand-aligned
    images, videos, and designs from natural-language briefs using 30+
    specialized AI pipelines.


    Unlike raw model APIs (DALL-E, Flux, Kling), Lamina automatically applies
    brand guidelines, selects the optimal multi-model pipeline, scores output
    quality, and delivers permanent CDN-hosted URLs with composability metadata
    for chaining.


    ## Authentication

    All endpoints require an API key passed via one of these headers:

    - `x-api-key: lma_your_key` (recommended)

    - `Authorization: Bearer lma_your_key`


    API keys are workspace-scoped. Create them in **Settings -> API Keys**.


    ## Quick Start (One-Call Path)

    `POST /v1/create` with `{ "brief": "product photo of sneakers", "sync": true
    }` — returns completed output inline with CDN URL.


    ## Quick Start (Advanced)

    1. `GET /v1/apps` -- browse 30+ specialized creative apps

    2. `GET /v1/apps/{appId}` -- inspect input parameters

    3. `POST /v1/apps/{appId}/runs?webhook=<url>` -- run with your inputs

    4. Receive results via webhook, poll `GET /v1/runs/{runId}`, or stream via
    `GET /v1/runs/{runId}/stream`


    ## Rate Limits

    All `/v1/*` endpoints are currently limited to 100 requests per minute per
    IP.

    On `429`, read the `RateLimit-*` and `Retry-After` headers before retrying.


    ## How Inputs Work

    When running an app, provide inputs as a JSON object keyed by parameter
    **name** (from the app details response).


    - **text** parameters: send a string value (e.g. a prompt or description)

    - **options** parameters: send the **label** of the option (e.g.
    `"Caucasian"`), not an internal value

    - **url** parameters: send a publicly accessible URL to an image or video


    Every parameter is returned with `required: true`. Parameters with a
    `default` can safely be omitted -- the app will use the default value.


    ## Endpoint Groups

    | Group | Purpose |

    |-------|---------|

    | **Apps** | Discover and inspect available content creation apps |

    | **Runs** | Run workflows, track progress, and retrieve results |

    | **Assets** | Browse generated images, videos, and text from past
    executions |

    | **Intelligence** | Brand context, performance prediction, recommendations,
    and trends |

    | **Publishing** | Publish content to connected social channels |

    | **Content** | One-call agent operations: create, score, brief, batch |

    | **Account** | Credit balance and rate limit visibility |

    | **Templates** | Content creation templates and strategies |

    | **Webhooks** | Webhook signature verification |
servers:
  - url: https://app.uselamina.ai
    description: Production
security:
  - apiKey: []
tags:
  - name: Create
    description: Create content from briefs, run specific apps, and use templates
  - name: Discover
    description: Find and inspect available apps and their input schemas
  - name: Track
    description: Monitor execution progress, retrieve results, and browse generated assets
  - name: Intelligence
    description: >-
      Brand context, performance prediction, content recommendations, and trend
      signals
  - name: Distribute
    description: Publish content to social channels and transfer assets to CDN
  - name: Score
    description: Evaluate content quality, brand alignment, and engagement potential
  - name: Compose
    description: >-
      Turn a script into a narrated, multi-shot video — pick a format (never a
      model), preview cost, and render
  - name: Account
    description: Credit balance, rate limits, and health checks
  - name: Webhooks
    description: Webhook signature verification for secure callback handling
paths:
  /v1/compose/video:
    post:
      tags:
        - Compose
      summary: Compose Narrated Video
      description: >
        Compose a **narrated, multi-shot** video from a script — not a single

        ~5s clip. Synthesizes the narration, storyboards it into beats,
        generates

        a continuity-chained shot per beat, and renders with transitions +

        Ken-Burns + captions.


        **Asynchronous** — composition takes minutes. You get a `runId`

        immediately; poll `GET /v1/compose/video/{runId}` (the snapshot carries

        stage `progress`) or use it after `POST /v1/compose/plan` to confirm
        cost.


        **Steer the render via the script, never models:** optional `direction`

        (free text) and `sections` (per-span hints). Pass `idempotencyKey` to
        make

        retries safe (a repeated key returns the same run — no double charge).
      operationId: composeVideo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ComposeInput'
            example:
              title: How Lamina composes video
              script: >-
                Most tools hand you a 5-second clip. Lamina scripts the whole
                story, then films it shot by shot — narration, captions, and
                all.
              direction: Confident, fast-paced; open on the product, end on the logo.
              aspectRatio: '9:16'
              idempotencyKey: demo-2026-07-26-001
      responses:
        '202':
          description: Composition started. Poll the run for progress + results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
              example:
                data:
                  runId: cmp-lz9x2k-a1b2c3
                  status: running
        '400':
          description: >-
            Invalid input. `details.reason` is machine-readable
            (`SCRIPT_REQUIRED`, `SCRIPT_TOO_LONG`, `FORMAT_UNAVAILABLE`,
            `MISSING_REQUIRED_INPUT`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ComposeError'
              example:
                error: 'format "talking_head" requires input(s): presenterImage'
                code: VALIDATION_ERROR
                retryable: false
                details:
                  reason: MISSING_REQUIRED_INPUT
                  formatId: talking_head
                  missingInputs:
                    - presenterImage
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  schemas:
    ComposeInput:
      type: object
      description: >
        Shared input for `POST /v1/compose/plan` and `POST /v1/compose/video`.
        The user picks a **format** (never a model) and steers the render via
        the **script** — `direction` + `sections` — not by naming models.
      required:
        - script
      properties:
        script:
          type: string
          maxLength: 4000
          description: >-
            The narration to speak (a hook + one insight + a CTA). Its spoken
            length must be ≤ the `maxScriptDurationSec` capability (~90s).
        title:
          type: string
          description: Video title.
        generationBrief:
          type: string
          description: One-paragraph visual brief for the shots.
        format:
          type: string
          description: >-
            A format id from `GET /v1/compose/formats`. Omit to auto-route the
            script to the best available format.
        direction:
          type: string
          description: >-
            Free-text creative direction ("open on the product, fast cuts, end
            on the logo") — steers the render without naming models.
        sections:
          type: array
          description: Structured per-span storyboard hints, for a prescriptive caller.
          items:
            type: object
            required:
              - hint
            properties:
              text:
                type: string
                description: The span of narration this hint applies to.
              hint:
                type: string
                description: What to show / how to treat this span.
              mustShow:
                type: boolean
                description: Hard requirement for this beat.
        inputs:
          type: object
          description: >-
            A format's required inputs, keyed by input key (e.g. a
            `presenterImage` key mapping to an https URL). See each format's
            `inputs` in `GET /v1/compose/formats`.
        aspectRatio:
          type: string
          enum:
            - '16:9'
            - '9:16'
            - '1:1'
            - '4:5'
            - '4:3'
          default: '16:9'
        voiceId:
          type: string
          description: >-
            ElevenLabs voice id for the narration. Omit for the default voice.
            (compose/video only.)
        clipSeconds:
          type: integer
          description: Target per-clip length within the capability window.
        idempotencyKey:
          type: string
          description: >-
            Dedupe retries — a repeated key returns the same run (no double
            charge). (compose/video only.)
    ComposeError:
      type: object
      description: >
        Structured error from the compose endpoints. `details.reason` is a
        stable, machine-readable sub-code an agent can branch on to
        self-correct.
      required:
        - error
      properties:
        error:
          type: string
          description: Human-readable error message.
        code:
          type: string
          description: The /v1 error-taxonomy code (drives HTTP status + retryable).
          example: VALIDATION_ERROR
        retryable:
          type: boolean
          description: Whether retrying the same request could succeed.
        details:
          type: object
          description: >-
            Machine-readable context. Always carries `reason`; other keys depend
            on the reason.
          required:
            - reason
          properties:
            reason:
              type: string
              enum:
                - SCRIPT_REQUIRED
                - SCRIPT_TOO_LONG
                - FORMAT_UNAVAILABLE
                - MISSING_REQUIRED_INPUT
                - NO_ELIGIBLE_FORMAT
              description: Stable sub-code for the failure.
            missingInputs:
              type: array
              items:
                type: string
              description: Present for MISSING_REQUIRED_INPUT — the input keys to supply.
            maxScriptDurationSec:
              type: integer
              description: Present for SCRIPT_TOO_LONG.
            estimatedDurationSec:
              type: integer
              description: Present for SCRIPT_TOO_LONG.
          additionalProperties: true
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
          example:
            error: Invalid API key
    TooManyRequests:
      description: >-
        Rate limit exceeded. All `/v1/*` endpoints are currently limited to 100
        requests per minute per IP.
      headers:
        RateLimit-Policy:
          description: Active rate limit policy.
          schema:
            type: string
            example: 100;w=60
        RateLimit-Limit:
          description: Max requests allowed in the current window.
          schema:
            type: integer
            example: 100
        RateLimit-Remaining:
          description: Requests remaining in the current window.
          schema:
            type: integer
            example: 0
        RateLimit-Reset:
          description: Seconds until the current window resets.
          schema:
            type: integer
            example: 60
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
            example: 60
      content:
        text/plain:
          schema:
            type: string
          example: Too many requests from this IP, please try again in a minute
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: 'Workspace API key. Prefix: `lma_`. Example: `lma_abc123...`'

````