# Batch Create Content Source: https://docs.uselamina.ai/api-reference/batch-create POST /v1/content/batch Create multiple content pieces in a single request. Brand context is resolved once and shared across all items for consistency and efficiency. Each item in the `items` array is an independent content creation request with its own brief and optional overrides. All items are executed in parallel. **Limits:** Maximum 10 items per batch. **Response:** Returns immediately with a `batchId` and per-item status. Items that could not be started are marked as `failed` with an error message. Successful items are `queued` with a run ID you can poll individually. **Agent usage pattern:** 1. Generate briefs with `POST /v1/content/brief` (count: 5) 2. Feed all briefs into this endpoint as a batch 3. Poll each `runId` from the response for results Create multiple pieces of content in a single request. Each item follows the same logic as the create-content endpoint — brief resolution, app selection, and execution — but all items run in parallel. Brand context is resolved once and shared across the batch. Returns an array of results, one per item. Each successful item includes a run ID you can track via the wait or get-run endpoints. Failed items include the error reason. Maximum 10 items per batch. # Compose Narrated Video Source: https://docs.uselamina.ai/api-reference/compose-video POST /v1/compose/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. # Generate Content Brief Source: https://docs.uselamina.ai/api-reference/create-brief POST /v1/content/brief Generate content briefs from a goal, enriched with brand context and trend signals. Returns multiple brief suggestions that can be fed directly into `POST /v1/content/create`. **Example workflow:** 1. Call this endpoint with a goal: "Increase Instagram engagement for our sneaker line" 2. Review the generated briefs (each is a self-contained content creation instruction) 3. Pass the best brief as the `brief` field to `POST /v1/content/create` Each generated brief incorporates: - Workspace brand DNA and voice attributes - Current trend signals for the target platform - Historical performance patterns Generate structured content briefs grounded in your brand context, trend signals, and performance data. Each brief includes a creative direction, suggested format, target platform, and recommended app. Use this as a planning step before content creation, or feed briefs directly into the create-content endpoint for fully automated production. Returns up to `count` briefs (default 3, max 10) per request. # Create Content Source: https://docs.uselamina.ai/api-reference/create-content POST /v1/content/create Compatibility endpoint over Lamina's canonical frozen-plan lifecycle. Interactive calls return clarification questions, route choices, or an approval-ready plan without dispatching generation. Unattended calls must set `headless: true`, send `Idempotency-Key`, and provide a positive `maxCredits`; they execute only an approval-ready plan within that ceiling. One-call creation. Describe what you want, and the API handles app selection, input mapping, brand context injection, and execution. Instead of manually choosing an app and mapping inputs, pass a `brief` describing the content you need. The engine resolves brand context, selects the best-fit app, injects creative guidance, and starts the execution. The operation is asynchronous — use the returned run ID with the wait, stream, or get-run endpoints to retrieve results. Best for agent-driven workflows where you want a single call to go from intent to finished asset. ## Headless mode (unattended agents) By default, `create` may return `status: "needs_input"` with `askUser` prompts when the chosen path needs information a human would supply — a dead end for an unattended worker or agent. Pass `headless: true` (or `terminalMode: "auto"`) to make the call reliable for agentic callers. In headless mode the engine: * **forces the self-drafting recipe path** and dispatches best-effort — it **never returns `needs_input`**; * returns `status: "unmatched"` (machine-actionable) if a run genuinely can't be formed, instead of an interactive ask; * surfaces any unmet asks on the `ran` response as structured `unmetAsks[]` — each `{ name, key, question, type, required, autoFillable }` — so an agent can satisfy one (e.g. generate an `image_url` via `POST /v1/generate/image`) and re-run. A pinned `appId` is ignored in headless mode. See the [Agent Creation Loop](/guides/agent-creation-loop) guide for the full create → satisfy → run pattern. # Generate App Source: https://docs.uselamina.ai/api-reference/generate-workflow POST /v1/workflows/generate Generate a brand-new app from a plain-language instruction when no existing app fits (check `POST /v1/apps/discover` first). A headless LLM planner assembles a validated workflow graph from the node catalog and auto-publishes it as a private, immediately-runnable app. The result is a normal app: run it via `POST /v1/apps/{appId}/runs` with the returned parameter keys. Pass `baseAppId` to EDIT an existing generated app in place: the planner starts from that app's current graph and applies the instruction as a targeted change, keeping the same appId and run history. Requires creator or workspace owner/admin. Generate a brand-new app from a plain-language instruction when no existing app fits your goal. Check `POST /v1/apps/discover` first — if a suitable app already exists, run that instead. A headless LLM planner reads your instruction, assembles a validated workflow graph from Lamina's node catalog, and auto-publishes it as a **private** app. The result is a normal app: run it immediately with `POST /v1/apps/{appId}/runs` using the returned parameter keys, and share it with `POST /v1/apps/{appId}/visibility`. ## One-shot generate + run To build **and** execute in a single call — e.g. text → workflow → video — pass `run: true` with an `inputs` map keyed by the app's parameter `key`s (media as URLs, options as labels). The response then includes a `run` object with the `runId`; poll `GET /v1/runs/{runId}` for the result. The app is always created; if inputs are invalid or a required input is missing, `run.started` is `false` and `run.errors` lists the problems so you can fix them and run the app directly. ## On-brand generation Generated apps are **brand-aware by default**. Before planning, Lamina resolves the workspace's brand DNA — voice, visual anchors, and guardrails — and the planner seeds the generation nodes so the app produces on-brand output without you hand-writing brand instructions into every run. Pass `brandProfileId` to target a specific brand in a multi-brand workspace; omit it to use the workspace's active brand. If no brand is configured, generation proceeds brand-agnostic — brand context is an enhancement, never a requirement. ## Writing a good instruction Describe **what the app produces**, **the inputs the user will supply**, and **the desired output**. The more concrete, the better the plan. * Good: *"Product photo app: the user uploads a product image and types a background scene; output one 1:1 image."* * Good: *"Voiceover app: the user pastes a script; output an audio voiceover."* ## Response Returns the app schema — `appId`, `name`, `parameters[]` (the inputs to pass to a run), `outputs[]`, and a `runUrl`. Feed the parameter `key`s straight into `POST /v1/apps/{appId}/runs`. On the planner path the response may also include **`editability`** — a `{ score, subscores, notes[] }` object measuring how tweakable the generated app is (parameter clarity, grouping, node granularity, whether a control steers the output). A low `score` with its `notes` tells you exactly what to improve; edit the app in place (pass `baseAppId` plus `ops` or a fresh `instruction`) to refine it. If the planner cannot produce a valid app, the response is a `422` with `error` and `details[]` describing the unresolved problems. ## Provider The `provider` field (`claude` or `openai`) overrides the server default for a single request — useful for A/B comparison. Omit it to use `WFGEN_PROVIDER`, which falls back to OpenAI. OpenAI-compatible Moonshot/Kimi targets are configured server-side by orchestration role rather than selected by public request fields. # Get App Details Source: https://docs.uselamina.ai/api-reference/get-app GET /v1/apps/{appId} Returns app metadata and the full list of input parameters the app accepts. Use this to understand what inputs you need to provide when running the app. Each parameter has a `type` that determines what value to send: - **text**: A free-form string (prompts, descriptions, product names) - **options**: Pick one label from the `options` array (e.g. `"Caucasian"`, `"Male"`, `"Studio"`) - **url**: A publicly accessible URL pointing to an image or video Use the parameter `name` as the key when providing `inputs` to the Run App endpoint. Inspect an app's input schema before running it. The `parameters` array is the source of truth for: * which parameters have a `default` (safe to omit) and which must be supplied * which values are valid for `options` type parameters * which fields expect public URLs * which parameter `key` (or `name`) values to use as keys in the `inputs` object — prefer `key`, the stable snake\_case identifier Always call this before `POST /v1/apps/{appId}/runs` — never guess parameter names or option values. # Get App Workflow Source: https://docs.uselamina.ai/api-reference/get-app-workflow GET /v1/apps/{appId}/workflow Returns the underlying workflow graph for an app -- the processing nodes and the edges (connections) between them. Useful for understanding how an app transforms inputs into outputs. Each node represents a processing step (e.g. image generation, video creation, upscaling). Edges show the data flow between nodes. Inspect the DAG (nodes and edges) behind an app. Each node is a processing step — image generation, video creation, compositing, upscaling — and edges show the data flow between them. Useful for agents that need to reason about what an app does before running it, or for building tooling that classifies apps by pipeline structure. # Get Brand Context Source: https://docs.uselamina.ai/api-reference/get-brand-context GET /v1/intelligence/brand-context Retrieve the complete brand context package for your workspace: brand DNA, workflow guidance, and top-performing content patterns. This is the primary intelligence endpoint -- use it to inform content creation decisions. The response combines three data sources resolved in parallel: - **brandDna** -- Voice attributes, visual identity, content pillars, audience signals, guardrails, tone spectrum, and performance profile extracted from the workspace's brand profile. - **guidance** -- Prompt directives, negative prompts, recommended creative moves, test ideas, winning/weak patterns, supporting metrics, and creative structure. Resolved from the active workflow guidance package, optionally scoped by campaign, workflow, platform, or objective. - **topPatterns** -- The highest-performing content items and aggregated pattern analysis (winning patterns vs weak patterns with occurrence counts and average performance scores). All parameters are optional. Omit them to get the workspace default context. Provide `brandProfileId` to scope to a specific brand profile, `campaignId` or `workflowId` to narrow guidance, and `platform`/`objective`/`modality` to get platform-specific recommendations. Retrieve the intelligence layer's understanding of your brand: voice attributes, visual identity, content pillars, audience signals, guardrails, and performance patterns. The Creative Engine uses this context automatically when you call `POST /v1/content/create`. Use this endpoint when you want to inspect what the engine knows, feed brand data into your own pipelines, or build brand-aware agent prompts. # Get Compose Run Source: https://docs.uselamina.ai/api-reference/get-compose-run GET /v1/compose/video/{runId} Poll a compose run. While running, `progress` reports the stage (`narrating` → `storyboarding` → `generating_shots` N/total → `assembling` → `done`). On completion, `result` carries the video URL. Poll a compose run. While running, `progress` reports the stage (`narrating` → `storyboarding` → `generating_shots` N/total → `assembling` → `done`). On completion, `result` carries the `videoUrl`, `durationSeconds`, and `shotCount`. # Get Run Status Source: https://docs.uselamina.ai/api-reference/get-execution GET /v1/runs/{runId} Poll this endpoint to check execution progress and retrieve results. **Execution status:** `queued` -> `running` -> `completed` or `failed` **Output status:** `pending` -> `completed` or `error` **Output type:** `pending` while processing, then `image`, `video`, or `text` once produced. Poll every 3-5 seconds. Stop when execution `status` is `completed` or `failed`. Check the current status and outputs of an execution. For agents, prefer `GET /v1/runs/{id}/wait` — it blocks until the execution finishes, so you don't need a polling loop. Use this endpoint when you need to check status without blocking, or as a fallback. Status progresses: `queued` → `running` → `completed` or `failed`. Outputs start as `pending` placeholders and resolve to `image`, `video`, or `text` with a URL or content value. # Get Publish History Source: https://docs.uselamina.ai/api-reference/get-publish-history GET /v1/publishing/history List past publish operations for the authenticated user. Returns publish records including platform, content URLs, captions, post URLs, and status. Use this to verify past publishes, track which content went to which platforms, and audit publish failures. Returns the publish history for your workspace, including status and destination details for each publish job. Use this to audit what has been published, track pending or failed publishes, and build dashboards showing content distribution activity. Results are paginated and ordered by publish time (newest first). # Get Recommendations Source: https://docs.uselamina.ai/api-reference/get-recommendations GET /v1/intelligence/recommendations List open content recommendations for the workspace. Recommendations are generated by the Content Intelligence Engine based on brand context, trend signals, and historical performance data. Each recommendation includes a type (e.g. `trend_opportunity`, `gap_analysis`, `optimization`), priority level, title, summary, and a `data` object with structured details. Use recommendations to surface actionable content ideas for agents or human creators. Combine with `POST /v1/content/create` to act on a recommendation by passing its details as the content brief. Returns actionable content recommendations grounded in your brand context and recent performance data. Recommendations cover what to create next, which formats to prioritize, and how to improve existing content. Use this to drive editorial calendars, feed agent-based content planners, or surface suggestions in creative tools. # Get Template Source: https://docs.uselamina.ai/api-reference/get-template GET /v1/templates/{id} Get full template details including guidance data and multi-asset composition. The `guidanceData` section contains the structured guidance that will be injected into the content creation process when this template is used: - `promptDirectives` -- Positive instructions for the AI generation prompt - `negativePrompts` -- Things to avoid in generation - `winningPatterns` -- Patterns known to perform well - `testIdeas` -- Suggested A/B test variations - `creativeStructure` -- Optional structured creative direction The `multiAssetComposition` section (if present) describes a multi-asset content piece with roles for each asset (e.g. hero image, supporting image, caption). Returns full details for a specific content template, including the recommended app, pre-filled inputs, and customizable parameters. Use this to inspect what a template provides before using it as the basis for a content creation request. The response includes enough context for an agent or UI to show the template's purpose, preview its defaults, and let the user override specific fields. # Get Trends Source: https://docs.uselamina.ai/api-reference/get-trends GET /v1/intelligence/trends Aggregate trending content signals for the workspace. Returns trend patterns detected across the workspace's content landscape over a configurable time window. Use this to identify what is gaining traction, discover emerging opportunities, and feed trend data into content briefs or creation decisions. Returns current trend signals relevant to your workspace's content categories and audience. Trends are sourced from cross-platform performance data and refreshed regularly. Use this to discover what topics and formats are gaining traction, inform content strategy, or feed trend data into automated brief generation. # Get Usage Source: https://docs.uselamina.ai/api-reference/get-usage GET /v1/account/usage Check remaining credits and rate limit status for the workspace. Use this before starting expensive operations (batch creates, video generation) to verify sufficient credit balance. The `rateLimit` section shows the current rate limit configuration (requests per window). **Credit model:** Each app execution consumes credits based on the processing nodes involved. Image generation, video generation, and AI text generation each have different credit costs. Returns the workspace's credit balance and the API rate-limit configuration. Use this to monitor consumption, enforce budget limits in your integration, or display remaining credits in a dashboard. The response includes the current credit `balance`, lifetime `totalEarned` / `totalSpent`, a `manageUrl` for billing, and the `rateLimit` window (`limit`, `windowMs`). It does not return per-endpoint counters or a per-request spend breakdown. # Get Webhook Signing Key Source: https://docs.uselamina.ai/api-reference/get-webhook-signing-key GET /v1/webhooks/signing-key Returns the public key used to verify webhook signatures. When you receive a webhook callback, verify it's from Lamina by checking the ED25519 signature in the `X-Lamina-Webhook-Signature` header. **Verification steps:** 1. Get the public key from this endpoint (cache it -- it rarely changes) 2. Reconstruct the signed message: `.` 3. Verify the ED25519 signature against the message using the public key **Headers sent with each webhook:** - `X-Lamina-Webhook-Signature` -- ED25519 signature (hex-encoded) - `X-Lamina-Webhook-Timestamp` -- Unix timestamp (seconds) when signed - `X-Lamina-Webhook-Request-Id` -- Run ID (for idempotency) - `X-Lamina-Webhook-User-Id` -- User ID that triggered the run - `Content-Type: application/json` **Retry policy:** If your endpoint returns non-2xx or times out (15s), we retry 3 times with backoff: 5s, 30s, 2 minutes. Each retry has a fresh signature. **Replay protection:** Reject webhooks with timestamps older than 5 minutes. The signing key is returned as a JWK. Consume it directly -- the `x` field is a raw 32-byte Ed25519 public key (base64url), not a DER/SPKI blob. **Node.js verification example:** ```javascript const crypto = require('crypto'); // Fetch once at startup: const { keys } = await fetch('.../v1/webhooks/signing-key').then(r => r.json()); // const PUBLIC_JWK = keys[0]; function verifyLaminaWebhook(rawBody, signatureHex, timestamp, jwk) { const publicKey = crypto.createPublicKey({ key: jwk, format: 'jwk' }); const message = Buffer.from(timestamp + '.' + rawBody); const signature = Buffer.from(signatureHex, 'hex'); return crypto.verify(null, message, publicKey, signature); } ``` **Python verification example:** ```python import base64 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey # jwk = requests.get('.../v1/webhooks/signing-key').json()['keys'][0] # raw = base64.urlsafe_b64decode(jwk['x'] + '==') # 32-byte Ed25519 public key # public_key = Ed25519PublicKey.from_public_bytes(raw) def verify_lamina_webhook(raw_body, signature_hex, timestamp, public_key): message = f"{timestamp}.{raw_body}".encode() signature = bytes.fromhex(signature_hex) public_key.verify(signature, message) # Raises on failure ``` Returns the ED25519 public key for verifying webhook signatures. Cache this key. It rarely changes, and your webhook handler should use it to verify that incoming callbacks are genuinely from Lamina and have not been tampered with. # Health Check Source: https://docs.uselamina.ai/api-reference/health-check GET /v1/health Lightweight health probe. No authentication required. Use this from load balancers, orchestrators, or agent startup routines to verify the API is reachable before sending real requests. Lightweight health probe for load balancers, orchestrators, and agent startup routines. No authentication required. Returns the API version and server timestamp. Use this to verify the API is reachable before sending authenticated requests. # List Apps Source: https://docs.uselamina.ai/api-reference/list-apps GET /v1/apps Search and discover apps you can run. Returns your own workspace apps and public apps from other workspaces. Use `search` to find apps by name or description. Discover what the Creative Engine can do. Returns apps your workspace can run, plus public apps from other workspaces. Use `search` to find apps by capability — `"catalog"`, `"try on"`, `"video"`. Each app has a stable input contract, so the same integration code works across all of them. If you're using `POST /v1/content/create`, app selection is automatic. Use this endpoint when you want to pick the app yourself. # List Assets Source: https://docs.uselamina.ai/api-reference/list-assets GET /v1/assets List generated assets (images, videos, audio, text) from completed executions in your workspace. Use this to browse all content your workspace has produced, filter by execution or output type, and retrieve CDN URLs for downstream use (publishing, further processing, etc.). Only assets from completed executions with status `completed` are returned. Results are ordered by creation time, newest first. Returns generated assets (images, videos, text) from past executions in your workspace. Use this endpoint when you need to browse outputs across multiple executions, build a media library view, or let users pick previously generated assets as inputs for new runs. Results are paginated and can be filtered by media type. # List Channels Source: https://docs.uselamina.ai/api-reference/list-channels GET /v1/publishing/channels List social accounts connected to the workspace. These are the channels you can publish content to via `POST /v1/publishing/publish`. Each channel includes the platform, account name, username, and whether an Instagram business account is linked (for Facebook pages with Instagram). Only valid (non-expired) connections are returned. Discover connected distribution destinations — Instagram, TikTok, YouTube, and other social accounts linked to your workspace. Call this before `POST /v1/publishing/publish` to get the `accountIds` you'll need. Each channel shows the platform, username, and account type. # List Compose Formats Source: https://docs.uselamina.ai/api-reference/list-compose-formats GET /v1/compose/formats List the video **formats** `POST /v1/compose/video` can render — animated slideshow, cinematic b-roll, motion-graphics explainer, and more. Each format renders differently and internally uses different models; **callers pick a format (never a model) and see its cost.** The response also returns fixed **capabilities** (max script length, supported aspect ratios, clip window) so an agent can construct any valid compose call from a single discovery request. Formats with `available: false` are on the roadmap and cannot be dispatched yet. Formats may declare required `inputs` (e.g. a presenter image) that must be supplied. The menu `POST /v1/compose/video` renders from. Each **format** is a named look — animated slideshow, cinematic b-roll, motion-graphics explainer, and more — that internally uses different models. You pick a **format**, never a model, and see its cost. The response also returns fixed **capabilities** (max script length, aspect ratios, clip window) so you can build any valid compose call from this one request. Formats with `available: false` are on the roadmap. A format may declare required `inputs` (e.g. a presenter image) you must supply when you dispatch it. Each format may include a **`previewUrl`** (a short sample render of the template) and **`thumbnailUrl`** (a poster) so you can *show* the user what each format looks like — not just its cost — before they pick one. Both are `null` until a preview has been generated. # List Runs Source: https://docs.uselamina.ai/api-reference/list-executions GET /v1/runs List past runs for the workspace with optional filters. Returns a paginated list of run summaries (without full output details). Use this to find runs to inspect, or to monitor recent activity. For full output details on a specific run, use `GET /v1/runs/{runId}`. Results are ordered by creation time, newest first. List past executions for your workspace, ordered by creation time (newest first). Use this to build execution history views, audit logs, or find a specific run you need to inspect. Supports filtering by status, app ID, and time range. Results are paginated with offset/limit. Default limit is 25, maximum 100. # List Templates Source: https://docs.uselamina.ai/api-reference/list-templates GET /v1/templates List available content templates (both built-in and custom workspace templates). Templates provide structured guidance for content creation -- prompt directives, negative prompts, winning patterns, test ideas, and multi-asset compositions. Use templates with `POST /v1/content/create` by passing the template `id` as the `templateId` field. The content endpoint will apply the template's guidance to the content creation process. **Template categories:** - `launch` -- New product or feature launches - `trend` -- Capitalizing on trending content patterns - `campaign` -- Campaign-specific content strategies - `optimization` -- Improving existing content performance - `testing` -- A/B testing and experimentation strategies - `library` -- General-purpose content patterns Returns content creation templates available to your workspace. Templates are pre-configured starting points for common content types -- product hero shots, social media posts, campaign banners, and more. Each template specifies a recommended app and pre-filled input values. Use this to offer quick-start options in your UI or to let agents pick the right template for a given content goal. # Plan Compose Video Source: https://docs.uselamina.ai/api-reference/plan-compose-video POST /v1/compose/plan **Preview** what `POST /v1/compose/video` will do for a script — the format it routes to, the estimated cost, and the per-beat storyboard — **without generating any media or spending a credit.** Fast (one LLM call). Use it to show the plan + cost and confirm before committing. `ready: false` with `missingInputs` / `blockers` tells you exactly what to supply before dispatching. Accepts the same steering fields as `/v1/compose/video` (`format`, `direction`, `sections`, `inputs`, `aspectRatio`). Preview what a compose call will do — the format it routes to, the estimated cost, and the per-beat storyboard — **without generating anything or spending a credit.** Fast (one LLM call). Call this first to show the user the plan + cost and confirm before committing. `ready: false` with `missingInputs` / `blockers` tells you exactly what to supply. Accepts the same steering fields as the compose call: `format`, `direction`, `sections`, `inputs`, `aspectRatio`. # Predict Content Performance Source: https://docs.uselamina.ai/api-reference/predict-performance POST /v1/intelligence/predict Predict how a content concept would perform on a given platform before creating it. Returns a performance prediction based on the workspace's historical data and brand context. Use this to validate ideas before spending credits on content creation, or to compare multiple concepts and pick the strongest one. The `concept` field is a natural-language description of the content you are considering -- it does not need to be a polished prompt. For example: "A lifestyle photo of our new sneakers in an urban setting with warm golden-hour lighting." Predict how a content concept will perform on a given platform before you create or publish it. Send a text `concept` describing the content idea along with the target `platform` and `modality`. Returns a performance prediction grounded in your workspace's historical data and brand context. Use this in pre-creation decision flows, A/B content selection, or automated quality gates to prioritize high-performing concepts. # Publish Content Source: https://docs.uselamina.ai/api-reference/publish-content POST /v1/publishing/publish Publish content (image, video, or caption) to one or more connected social accounts. Provide at least one of `imageUrl`, `videoUrl`, or `caption`. Use `GET /v1/publishing/channels` to discover available channel IDs. **Important:** URLs must point to publicly accessible media. If you have a generated asset from an execution, you may need to transfer it to CDN first using `POST /v1/publishing/transfer-asset` to ensure it remains available. Publish content to one or more connected social channels. Provide the asset (image URL, video URL, or both), a caption, and the target `accountIds` from the list-channels endpoint. You must provide at least one of `imageUrl`, `videoUrl`, or `caption`. Use the publish-history endpoint to track the status of past publishes. # Refine Run Source: https://docs.uselamina.ai/api-reference/refine-run POST /v1/runs/{runId}/feedback Iterative refinement: provide natural-language feedback on a completed run's outputs. An LLM identifies which workflow nodes need prompt changes, re-executes them, and returns updated outputs with a summary of what changed. Only works on runs in `completed` status. The refinement happens in-place on the same run — no new run ID is created. **Example:** After generating a product image, send `"Make the background brighter and add more contrast"` to refine the output without re-running the entire workflow. Iterative refinement for completed runs. Provide natural-language feedback and the engine re-executes only the nodes that need to change. The feedback agent uses an LLM to analyze which workflow nodes (image generation, video editing, etc.) need prompt modifications to address the feedback, rewrites only those prompts, and re-executes the affected subgraph. Use this for quality feedback loops: generate content, review it, then refine without starting over. # Run App Source: https://docs.uselamina.ai/api-reference/run-app POST /v1/apps/{appId}/runs Start an asynchronous app execution. Returns immediately with an execution ID and pre-created output placeholders. **Providing inputs:** - Use the `inputs` object with parameter **names** as keys (from the Get App response) - For `options` parameters, send the **option label** (e.g. `"Caucasian"`, not an internal value) - For `url` parameters, send a publicly accessible URL - For `text` parameters, send a string value - Omit optional parameters to use their defaults **After starting:** You can poll `GET /v1/runs/{runId}` every 3-5 seconds, use a **webhook** to receive results automatically, stream via `GET /v1/runs/{runId}/stream`, or combine approaches -- they all work side by side. **Webhook (optional, recommended for agents):** Pass `?webhook=https://your-server.com/callback` as a query parameter. When the execution completes, we POST the results to your URL -- same structure as the polling response, signed with ED25519 for verification. The polling endpoint still works regardless, so you can use it as a fallback or for safety checks. See the Webhook Verification endpoint for details. Start an execution of a specific app. You choose the app and provide exact inputs. For automatic app selection, use `POST /v1/content/create` instead. Key each field in `inputs` by the parameter's `key` (stable snake\_case identifier) or `name` from `GET /v1/apps/{appId}`. Prefer `key` when present — it never changes, while `name` is a display label and matches case-sensitively. ## On-brand runs Set `applyBrand: true` to make the output on-brand without hand-writing brand into every input. Lamina folds the workspace's brand negatives and visual style onto the app's visual generation nodes before the run. Anything you pass in `inputs` still wins — brand only fills fields you left unset, and never touches a field the app exposes as a parameter. Pass `brandProfileId` to target a specific brand in a multi-brand workspace; omit it for the workspace's active brand. ## Sandbox / test mode Build and CI your integration without spending credits. Send the header `X-Lamina-Test: true` (or `"test": true` in the body) on a run or on `POST /v1/workflows/generate`. Lamina validates the request exactly as it would for real — bad inputs still return `400` — then returns a deterministic stub (`status: "test"`, a `test_…` id) **without dispatching an execution, calling any model, or charging credits**. Point your tests at it to exercise your request-building and response-handling for free, then drop the header to go live. ## Getting results | Method | Best for | | ----------------------------------------- | --------------------------------------------------------- | | **Wait** — `GET /v1/runs/{id}/wait` | Agents. Blocks until done, no polling loop. | | **Webhook** — pass `?webhook=` | Production. Results POST to your URL on completion. | | **SSE** — `GET /v1/runs/{id}/stream` | Real-time UIs. Server-Sent Events with per-node progress. | | **Poll** — `GET /v1/runs/{id}` every 3-5s | Fallback when none of the above fit. | # Score Content Source: https://docs.uselamina.ai/api-reference/score-content POST /v1/content/score Evaluate workspace content across multiple quality dimensions. Scores are computed based on brand alignment, predicted engagement, visual quality, and platform fitness. You can either: - Provide specific `contentItemIds` to score particular items - Omit `contentItemIds` and use `platform`/`modality`/`limit` filters to score recent workspace content matching those criteria Use scores to identify your strongest content, find items that need improvement, and prioritize what to publish. Evaluate workspace content across multiple quality dimensions including brand alignment, visual quality, and engagement potential. Pass `contentItemIds` to score specific items, or use the `platform` and `modality` filters to score recent content matching those criteria. Results include per-dimension breakdowns useful for automated quality gates, ranking content variants, or building review dashboards. # Set App Visibility Source: https://docs.uselamina.ai/api-reference/set-app-visibility POST /v1/apps/{appId}/visibility Change an app's reach: private (creator or workspace owner only), shared (any workspace member — discoverable via search), or public (anyone). Only the app creator or a workspace owner may change visibility. Change who can find and run an app. Only the app's creator or a workspace owner may change its visibility. | Visibility | Who can view & run | | ---------- | --------------------------------------------------------------------------------------------- | | `private` | The creator and the workspace owner only | | `shared` | Any member of the app's workspace — the app becomes discoverable via `POST /v1/apps/discover` | | `public` | Anyone | A common flow is to generate an app (which starts `private`), verify it with a run, then promote it to `shared` so the rest of the workspace — and their agents — can use it. ## Response Returns the updated app summary, including its new `visibility`. # Stream Run Source: https://docs.uselamina.ai/api-reference/stream-execution GET /v1/runs/{runId}/stream Opens a Server-Sent Events (SSE) stream for real-time execution progress. This is an alternative to polling `GET /v1/runs/{runId}` -- use whichever fits your architecture better. **Connection:** The stream uses standard SSE (`text/event-stream`). Most HTTP clients and all modern browsers support this natively. **Event types:** | Event | When | Data payload | |-------|------|-------------| | `progress` | Execution status changes (e.g. `queued` -> `running`) | Full `ExecutionStatus` object | | `complete` | All outputs are ready | Full `ExecutionStatus` with final values | | `error` | Execution failed or was cancelled | Full `ExecutionStatus` with error details | | `timeout` | Stream exceeded 10-minute limit | `{ "message": "Stream timeout after 10 minutes" }` | | `ping` | Keepalive every ~15 seconds | Unix timestamp | **Terminal behavior:** - If the execution is already in a terminal state (`completed`, `failed`, `cancelled`) when you connect, the stream sends a single `complete` or `error` event and closes. - Otherwise, the stream stays open and polls internally every 2 seconds. - The stream automatically closes after 10 minutes with a `timeout` event. **Agent usage pattern:** ``` const es = new EventSource('/v1/runs/{id}/stream', { headers: { 'x-api-key': 'lma_...' } }); es.addEventListener('complete', (e) => { const result = JSON.parse(e.data); // result.outputs contains final values es.close(); }); es.addEventListener('error', (e) => { const result = JSON.parse(e.data); console.error(result.errorMessage); es.close(); }); ``` Opens a Server-Sent Events (SSE) stream for real-time execution progress. Use this instead of polling when you need instant updates -- for example, to drive a progress bar, stream partial outputs to a UI, or feed status changes into an agent loop. The connection stays open until the execution reaches `completed` or `failed`. Each SSE event contains the current execution state, including per-node output status. The final event carries the full completed (or failed) execution payload, after which the server closes the stream. # Transfer Asset to CDN Source: https://docs.uselamina.ai/api-reference/transfer-asset POST /v1/publishing/transfer-asset Transfer an asset from an external URL to the Lamina CDN for reliable, permanent access. Use this when you need a stable URL for publishing or downstream consumption. Generated assets from executions are often stored on temporary provider URLs that may expire. Transfer them to CDN before publishing or sharing. Returns the permanent CDN URL that can be used with `POST /v1/publishing/publish` or any other system. Transfer a generated asset from an external URL to the Lamina CDN for reliable, long-term access. AI model providers often host generated assets on temporary URLs that expire. Use this endpoint to copy the asset to Lamina's CDN before the source URL goes away. Returns the permanent CDN URL. Supports `image`, `video`, and `audio` assets. # Wait For Run Source: https://docs.uselamina.ai/api-reference/wait-for-execution GET /v1/runs/{runId}/wait Block until the execution reaches a terminal state (`completed`, `failed`, or `cancelled`) or the timeout expires. This is a simpler alternative to SSE streaming for agents and HTTP clients that cannot consume event streams. The server polls internally and returns once the execution is done. If the timeout expires before the execution finishes, the response includes `timeout: true` alongside the current execution state. This is NOT an error — the execution is still running. You can call this endpoint again or switch to polling. **Recommended for agents:** Use `timeout=60` (default) for typical image workflows. Use `timeout=120` for video workflows. Long-poll endpoint that blocks until the execution finishes or the timeout expires. Simpler alternative to SSE streaming for agents and HTTP clients that cannot consume event streams. Use this when your agent runtime does not support `EventSource` or SSE. The server polls internally and returns once the execution reaches `completed`, `failed`, or `cancelled`. If the timeout expires first, the response includes `timeout: true` alongside the current execution state — this is not an error. ## Recommended timeouts * Image workflows: `timeout=60` (default) * Video workflows: `timeout=120` ## Compared to other result delivery methods | Method | When to use | | ------------------------ | ----------------------------------------------------------------------- | | **Wait (this endpoint)** | Agent can make HTTP calls but not consume SSE. Simplest integration. | | **SSE stream** | Need real-time progress events (node completions, intermediate status). | | **Webhook** | Production workloads where you don't want open connections. | | **Polling** | Fallback when none of the above fit. Poll every 3-5 seconds. | # Authentication Source: https://docs.uselamina.ai/authentication How authentication works, how workspace-scoped keys behave, and what to send on every request. ## Overview Lamina's public Apps API uses **workspace-scoped API keys**. The canonical public path is `/v1/...`. Send the key with either: ```http theme={null} x-api-key: lma_your_api_key ``` or: ```http theme={null} Authorization: Bearer lma_your_api_key ``` Use `x-api-key` unless you have a reason to standardize on bearer auth across your stack. ## Get Your API Key API keys are created from the Lamina dashboard: 1. Sign in at [app.uselamina.ai](https://app.uselamina.ai). 2. Go to **Settings → API Keys** ([app.uselamina.ai/settings?tab=api](https://app.uselamina.ai/settings?tab=api)). 3. Click **Create key**, name it (e.g. `production` or `staging`), and copy it. The raw key (prefix `lma_`) is shown **once, at creation time** — store it immediately in your secrets manager; you can't retrieve it again, only revoke and recreate. Creating a key requires an **owner or admin** role on the workspace. Prefer never handling a raw key at all? Use the CLI or an MCP client instead — both authenticate over OAuth in the browser with no key to copy. See [Using the CLI locally](#using-the-cli-locally) below and the [MCP install guide](/guides/mcp-oauth-install). ## What The Key Grants Access To An API key is bound to a Lamina workspace. That means the key determines: * which private apps can be discovered * which executions can be started * which execution records can be read * which workspace context is used for authorization When you call the Apps API with an API key, you do **not** need a JWT or cookie-based session. ## Workspace Scope Keys are workspace-scoped, not user-scoped. In practice this means: * your own workspace apps are available through the key * public apps from other workspaces may also be visible * requests are still checked against the key's workspace context If you send `x-workspace-id`, it must match the workspace associated with the API key. ## Example ```bash theme={null} curl -H "x-api-key: lma_your_api_key" \ https://app.uselamina.ai/v1/apps ``` ## Security Recommendations * Keep Lamina API keys on your server, not in browser code. * Rotate keys if they are exposed in logs, screenshots, or commits. * Create separate keys for staging and production. * Prefer one key per integration so usage is easier to audit. ### Using the CLI locally `lamina login` opens your browser for an OAuth approval flow — the same pattern used by `gh`, `supabase`, `vercel`, `firebase`. No copy-paste of secrets. ```bash theme={null} lamina login ``` For CI / scripted callers, pass an API key non-interactively: ```bash theme={null} lamina login --api-key lma_your_api_key ``` Either path persists credentials at `~/.lamina/config.json` (mode `0600`). OAuth access tokens auto-refresh in the background when they're near expiry; if the refresh token also expires (after 30 days), the CLI surfaces a clear "session expired" error pointing at `lamina login`. See [Use The CLI And SDK](/guides/use-the-cli-and-sdk) for the full command reference. ## Common Authentication Errors ### `401 Missing API key` No supported auth header was sent. ### `401 Invalid API key` The key is malformed, revoked, or unknown. ### `403 Workspace header does not match API key workspace scope` The request included an `x-workspace-id` value that does not match the key's workspace. ## Next Steps * Read [Quick Start](/quick-start) to make your first request * Read [Use The CLI And SDK](/guides/use-the-cli-and-sdk) to work from a terminal * Read [Apps and Executions](/concepts/apps-and-executions) to understand the lifecycle * Read [Integration Recipes](/guides/capability-recipes) for commerce, try-on, video, and media workflow patterns * Read [Run Your First App](/guides/run-your-first-app) for a concrete end-to-end execution example # Changelog Source: https://docs.uselamina.ai/changelog What's new in Lamina — the upgrades we ship, in plain language. Everything we ship that changes what you can do, newest first. Want it in your inbox? [Subscribe](https://uselamina.ai). Building on the API? The same releases show up in the [API reference](/api-reference/create-content). ## See exactly what you'll spend on — before you spend it The plan you approve is now legible at a glance, so there are no surprises in the output. * **Your images, shown as images.** Uploaded a product photo and a logo? The plan previews the actual pictures in each slot instead of an opaque file id — confirm the right asset landed before you approve. * **No stealth business claims.** If an app's stock copy would ship a factual claim about your business you never made (e.g. *"Only 4 stocks remaining"*), the plan flags it and holds approval until you keep or edit it. * **A pipeline you can read.** The node flow now names each step by its role — *Logo*, *Product Image*, *Remarketing hook*, *Final ad* — not a generic type, and every input shows a quiet source tag (your upload · from your brief · app default) on hover. * **Faster to skim.** Long plans read as a numbered list, not a run-on line. ## Your brand now steers the apps you run Pick a brand, and the copy an app generates — the hook, the offer, the tone — is drafted from your brand's guidelines, not a generic default. On-brand by default, no extra prompting. ## Jump from a plan into the editable Canvas Reviewing a plan for one of your own workflows? An **Open in Canvas** link takes you straight to the editable node graph to change nodes and connections — no hunting for it in your workflow list. ## Editing a workflow is a quick pick, not a scroll Asking to edit a workflow used to dump your entire library as options. Now the Director offers the few most relevant to what you're doing (and the search box handles the rest) — and a brief that just happens to *mention* workflows no longer gets mistaken for a request to edit one. # Apps And Executions Source: https://docs.uselamina.ai/concepts/apps-and-executions The core Lamina mental model: discover an app, inspect its inputs, run it asynchronously, then fetch the execution result. ## The Core Lamina Model ### App An **app** is a packaged workflow with a stable input contract. Think of an app as the unit you expose in your product. One app might generate product imagery, another might run try-on, and another might turn still assets into video. The integration pattern stays the same. You use app endpoints to: * discover what is available * inspect the app's parameters * optionally inspect the underlying workflow graph * start an execution ### Execution An **execution** is one asynchronous run of an app. Executions are durable server-side jobs. They may complete quickly for lightweight image tasks, or take longer for multi-step catalog and video workflows. ## Standard Flow Call `GET /v1/apps` to find an app you can run. Call `GET /v1/apps/{appId}` to see which inputs the app accepts. Call `POST /v1/apps/{appId}/runs?webhook=` with your inputs. Receive results via webhook callback, or poll `GET /v1/runs/{runId}` until the status is terminal. The point of this model is stability: your backend always works with `appId`, `runId`, `inputs`, and `outputs`, even as the workflows behind those apps evolve. ## Execution Lifecycle Executions move through a small set of top-level states: * `queued` * `running` * `completed` * `failed` For long-running apps, especially video generation, the request that starts execution returns quickly. Your integration should either pass a `?webhook=` URL to receive results automatically, or treat the execution ID as a job handle and poll for status. ## What Comes Back When you first start an execution, Lamina may return placeholder outputs with status `pending`. When the execution finishes: * output `type` changes from `pending` to something like `image`, `video`, or `text` * `value` contains the final result * `status` becomes `completed` for successful outputs If execution fails, inspect: * the top-level `errorMessage` * each output's `error` field ## When To Use Workflow Inspection `GET /v1/apps/{appId}/workflow` returns the node graph behind an app. Most product integrations do not need this endpoint. It is useful when you want to: * reason about what the app does internally * build richer agent tooling * classify apps by pipeline structure For most backend and product teams, the core API surface is still: * list apps * get app * run app * get execution # The Creative Engine Source: https://docs.uselamina.ai/concepts/creative-engine Lamina's core mental model: apps as creative schemas, executions as content operations, intelligence as editorial brain, distribution as delivery layer. ## What Is The Creative Engine Lamina is an agentic creative API for generating videos, movies, and images for products, brands, social, and ads. Instead of calling models directly, you call **apps** — packaged multi-node workflows that may internally chain image generation, video generation, LLM reasoning, compositing, upscaling, and audio synthesis. Each app has a typed input schema. You send inputs, the API handles orchestration, brand context injection, and output delivery. The engine sits between your application and the models. Your code never needs to know which providers run behind an app, how nodes are wired, or where outputs are hosted. You work with five primitives, and the engine handles everything else. ## The Five Primitives | Primitive | What It Is | CMS Analogy | | ---------------- | ------------------------------------------------ | ---------------------------------- | | **App** | A packaged workflow with a typed input schema | Content type / document schema | | **Execution** | One async run of an app that produces outputs | Document creation / mutation | | **Intelligence** | Brand context, predictions, recommendations | Editorial strategy / content rules | | **Template** | Pre-configured starting point for a content type | Content template / blueprint | | **Channel** | A connected publishing destination | Frontend / delivery endpoint | **Apps** define what you can create. **Executions** are the act of creating. **Intelligence** makes the output smarter. **Templates** accelerate setup. **Channels** deliver the result. This is the full content lifecycle through one API: discover, create, evaluate, distribute. ## Two Ways To Create ### One call: let the engine decide ```bash theme={null} curl -X POST \ -H "x-api-key: lma_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "brief": "A premium hero shot of a leather handbag on marble, warm studio lighting", "contentType": "product_image" }' \ https://app.uselamina.ai/v1/content/create ``` One call. The engine resolves brand context, selects the best-fit app, maps inputs, and starts the execution. You get back a run ID and collect results the same way as any other execution. This is the path for agents and automation — go from intent to asset without manual orchestration. ### Step by step: you pick the app ```bash theme={null} # 1. Discover curl -H "x-api-key: lma_your_api_key" \ https://app.uselamina.ai/v1/apps # 2. Inspect curl -H "x-api-key: lma_your_api_key" \ https://app.uselamina.ai/v1/apps/{appId} # 3. Execute curl -X POST \ -H "x-api-key: lma_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "inputs": { "Front": "https://example.com/front.jpg", "Location": "Studio" } }' \ "https://app.uselamina.ai/v1/apps/{appId}/runs?webhook=https://your-server.com/callback" ``` You choose the app, inspect its schema, provide exact inputs. Full control over every parameter. Both paths produce the same execution object, the same output structure, and the same delivery options. The difference is who does the thinking — your code or the engine. ## The Execution Lifecycle Every creation in Lamina — whether started via `content/create` or `apps/{appId}/runs` — follows the same lifecycle: The execution is accepted and waiting for capacity. You receive a run ID immediately. The workflow nodes are executing. For multi-step apps, individual nodes complete in sequence or parallel. Terminal state. Outputs resolve from `pending` placeholders to concrete `image`, `video`, or `text` values. Four ways to get results: | Method | When to use | | ----------------- | ----------------------------------------------------------------------------------- | | **Wait endpoint** | Simplest. `GET /v1/runs/{id}/wait` blocks until done. Best for agents. | | **SSE stream** | Real-time progress. `GET /v1/runs/{id}/stream` sends events as nodes complete. | | **Webhook** | Production workloads. Pass `?webhook=` at execution time. Signed with ED25519. | | **Polling** | Fallback. Poll `GET /v1/runs/{id}` every 3-5 seconds. | See [Handle Long-Running Executions](/guides/handle-long-running-executions) for implementation patterns. ## Intelligence Layer The intelligence endpoints are the editorial brain of the engine. They make creation smarter over time. | Endpoint | What it provides | | -------------------------------------- | ------------------------------------------------------------------ | | `GET /v1/intelligence/brand-context` | Brand DNA, visual guidelines, tone, creative constraints | | `POST /v1/intelligence/predict` | Performance prediction before you publish | | `GET /v1/intelligence/recommendations` | Actionable content suggestions based on brand and performance data | | `GET /v1/intelligence/trends` | Trend signals for your categories | When you use `POST /v1/content/create`, the engine queries brand context automatically and injects it into the execution. When you use the step-by-step path, you can fetch brand context yourself and use it to inform your input choices. Intelligence is what separates a creative engine from a model proxy. The engine knows your brand, predicts what will perform, and recommends what to create next. ## Distribution Layer Channels are connected publishing destinations — social accounts, CDN endpoints, storage targets. | Endpoint | What it does | | ------------------------------------ | ----------------------------------------------- | | `GET /v1/publishing/channels` | List connected social accounts and destinations | | `POST /v1/publishing/publish` | Publish content to one or more channels | | `POST /v1/publishing/transfer-asset` | Transfer assets to external CDN or storage | | `GET /v1/publishing/history` | View publish history and job status | The pattern is the same as a headless CMS delivering to frontends: your content is created once, stored as structured data, and delivered to any channel from one API. No per-channel integration work on your side. ## What This Means For Agents The Creative Engine is designed for programmatic consumption. If you are building an agent — whether with Claude, GPT, LangChain, or your own orchestrator — the API gives you: * **Schema-first discovery.** Every app has a typed input contract you can inspect at runtime. No guessing. * **Async-native execution.** Executions return immediately. Results arrive via webhook, SSE, wait endpoint, or polling. * **Intelligence grounding.** Brand context and performance data are API-accessible. Your agent can make informed creative decisions, not blind ones. * **One-call creation.** `POST /v1/content/create` goes from a text brief to a finished asset without manual app selection or input mapping. * **Structured outputs.** Every result is typed (`image`, `video`, `text`) with a stable schema. Parse once, handle every app. If you are building an agent, start with [Create Content](/api-reference/create-content). If you are building a product integration, start with [Quick Start](/quick-start). # Inputs And Outputs Source: https://docs.uselamina.ai/concepts/inputs-and-outputs How Lamina parameter types map to request payloads, and how to interpret execution outputs. ## Building Requests Safely When starting an execution, send an `inputs` object keyed by parameter **name** from `GET /v1/apps/{appId}`. Parameter names are case-sensitive and must match the app metadata exactly. This is what lets Lamina support different apps without changing the request envelope. Your code keeps sending `inputs`; only the parameter schema changes from app to app. Example: ```json theme={null} { "inputs": { "Front": "https://example.com/front.jpg", "Back": "https://example.com/back.jpg", "Model Gender": "Female" } } ``` ## Public Parameter Types | Type | What you send | Notes | | --------- | --------------- | -------------------------------------------- | | `text` | a string | prompts, descriptions, product names | | `options` | an option label | send the displayed label, not an internal ID | | `url` | a public URL | typically an image or video URL | ## Important Rules ### `options` For option parameters, send the **label** shown in the app metadata. Example: ```json theme={null} { "inputs": { "Location": "Studio" } } ``` ### `url` URLs should be publicly accessible by Lamina at execution time. Good sources include: * your own CDN * cloud object storage with public access * signed URLs that will remain valid long enough for processing If you use signed URLs, make sure they stay valid for the full processing window. ### Defaults Every parameter is returned with `required: true`. If it has a `default`, omitting it is safe — the app uses the default value. If it has no `default`, you must supply a value or the request is rejected with a `missing_no_default` error. ## Reading Results Executions return an `outputs` array. Each output object contains: * `id` * `label` * `type` * `value` * `status` * `error` Example completed output: ```json theme={null} { "id": "node-1", "label": "Generated Video", "type": "video", "value": "https://cdn.example.com/result.mp4", "status": "completed", "error": null } ``` ## Output Types Common output types include: * `image` * `video` * `text` * `pending` **Branch on `status`, not `type`, to decide if an output is finished.** `type` stays `"pending"` on failed outputs — only `status` flips to `"error"`. At execution start, outputs appear with `type: "pending"` and `value: null`; treat those as placeholders until `status` reaches `completed`, `error`, or `cancelled`. ## Agent Artifacts Agent-facing status responses also include an `artifacts` array. `outputs` remains the backward-compatible raw result list, while `artifacts` adds reuse metadata so agents can chain creative work safely. Each artifact includes: * `id`, `label`, `type`, `status`, and `error` * `url` when the artifact is downloadable media * `mimeType`, `dimensions`, and `durationSeconds` when known or inferable * `provider`, `model`, `cost`, and `prompt` when available * `reusableAs` roles such as `image_reference` or `video_reference` * `provenance` with `runId`, `workflowId`, node identity, and output index Use `artifacts` when an agent needs to reuse, publish, inspect, or pass a result into another Lamina run. Use `outputs` for compatibility with existing integrations. ## Practical Integration Pattern For admin tooling, merchant tooling, and agent-driven clients: 1. fetch the app metadata 2. render input controls from the parameter list 3. submit `inputs` keyed by parameter name 4. handle output rendering based on output type # Errors Source: https://docs.uselamina.ai/errors Structured error responses, machine-readable codes, retry guidance, and recovery patterns. ## Error Envelope Every `/v1/` error response uses a structured envelope: ```json theme={null} { "error": "Human-readable error message", "code": "AUTH_INVALID_KEY", "retryable": false, "requestId": "550e8400-e29b-41d4-a716-446655440000" } ``` | Field | Type | Always present | Description | | ------------ | ------- | -------------------------- | --------------------------------------------- | | `error` | string | Yes | Human-readable error message | | `code` | string | Yes | Machine-readable error code (see table below) | | `retryable` | boolean | Yes | Whether the request can be retried | | `retryAfter` | integer | Only on `429` | Seconds to wait before retrying | | `details` | array | Only on `VALIDATION_ERROR` | Per-field validation errors | | `requestId` | string | Yes | Trace ID from `X-Request-Id` header | **For agents:** branch on `code` for error handling, use `retryable` to decide whether to retry, and include `requestId` in support requests. ## Error Codes | Code | HTTP | Retryable | When | | ------------------------ | ---- | --------- | ---------------------------------------------------------------------------------- | | `AUTH_MISSING_KEY` | 401 | No | No API key in `x-api-key` or `Authorization` header | | `AUTH_INVALID_KEY` | 401 | No | Key hash not found or key is revoked | | `AUTH_INVALID_CONTEXT` | 401 | No | Key record is missing workspace or user context | | `AUTH_FAILED` | 500 | Yes | Database error during key validation | | `FORBIDDEN` | 403 | No | Workspace mismatch, app access denied, trigger inactive | | `NOT_FOUND` | 404 | No | App, execution, trigger, or template not found | | `VALIDATION_ERROR` | 400 | No | Invalid inputs, missing required fields, bad request body | | `CONFLICT` | 409 | Yes | Another request with the same `Idempotency-Key` is still in flight — retry shortly | | `RATE_LIMITED` | 429 | Yes | Request rate exceeded; check `retryAfter` | | `WEBHOOK_INVALID_URL` | 400 | No | Webhook URL malformed or unsupported protocol | | `WEBHOOK_INVALID_SECRET` | 401 | No | Webhook secret does not match | | `RESOURCE_UNAVAILABLE` | 503 | Yes | Service not configured or temporarily unavailable | | `INTERNAL_ERROR` | 500 | Yes | Unhandled server error | ## HTTP Status Codes | Status | Meaning | | ------ | ---------------------------------------------------------- | | `200` | Successful read | | `202` | Execution accepted and queued asynchronously | | `400` | Invalid request body or inputs | | `401` | Missing or invalid API key | | `403` | Access denied for this workspace or app | | `404` | App or execution not found | | `409` | Duplicate in-flight request for the same `Idempotency-Key` | | `429` | Rate limit exceeded | | `500` | Unexpected server-side failure | | `503` | Service temporarily unavailable | ## Interpreting `202 Accepted` `POST /v1/apps/{appId}/runs` returns `202` when Lamina has accepted the job. That does **not** mean outputs are ready yet. After a `202` response: * store the returned run ID * call `GET /v1/runs/{runId}/wait?timeout=60` (simplest) * or poll `GET /v1/runs/{runId}` every 3-5 seconds * or wait for your webhook callback ## Common Error Cases ### Authentication errors ```json theme={null} { "error": "Missing API key", "code": "AUTH_MISSING_KEY", "retryable": false, "requestId": "abc-123" } ``` Action: * `AUTH_MISSING_KEY`: send `x-api-key` header or `Authorization: Bearer ...` * `AUTH_INVALID_KEY`: verify the key value, check it has not been revoked, confirm correct environment * `AUTH_INVALID_CONTEXT`: the key exists but is misconfigured — contact support ### Invalid inputs Validation errors include a `details` array with one structured entry per problem: ```json theme={null} { "error": "Invalid inputs", "code": "VALIDATION_ERROR", "retryable": false, "requestId": "abc-123", "details": [ { "param": "Mention Key Ingredients", "code": "missing_no_default", "message": "\"Mention Key Ingredients\" is required: no default is configured for this parameter." }, { "param": "Location", "code": "invalid_option", "message": "\"Location\": invalid option \"Mars\". Must be one of: Studio, Urban, Park" } ] } ``` Each detail entry has: | Field | Type | Meaning | | --------- | -------------- | ------------------------------------------ | | `param` | string \| null | The offending parameter name | | `code` | string | A machine-readable detail code (see below) | | `message` | string | A human-readable explanation | **Detail codes:** | Code | When it fires | | -------------------- | ---------------------------------------------------------------------------- | | `missing_no_default` | A parameter has no default and you didn't supply a value for it | | `unknown_parameter` | The input key doesn't match any parameter on this app | | `invalid_option` | You sent an option value that isn't in the allowed list | | `invalid_media` | A `url` parameter failed media validation (bad URL, unreachable, wrong type) | | `invalid_type` | You sent the wrong JSON type (e.g. a number where a string was expected) | **About `required` and defaults:** every parameter in the schema is returned with `required: true`, so agents always know to supply a value. If a parameter has a `default`, omitting it is still safe -- the workflow will run with the default. If it has no `default`, omitting it triggers a `missing_no_default` error with `400`. **How to recover:** 1. Parse `details[]` and branch on `code`. 2. For `missing_no_default`: read the `param` field, look it up in your cached schema, and fill in a value. 3. For `unknown_parameter`: re-fetch `GET /v1/apps/{appId}` -- your cached schema may be stale. 4. For `invalid_option`: read the message for the allowed option list and pick one. 5. For `invalid_media` or `invalid_type`: fix the value and retry. ### Not found ```json theme={null} { "error": "App not found", "code": "NOT_FOUND", "retryable": false, "requestId": "abc-123" } ``` Action: * verify the `appId` or `runId` * confirm the resource is accessible to the workspace associated with your key ### Rate limit exceeded ```json theme={null} { "error": "Too many requests, please try again later", "code": "RATE_LIMITED", "retryable": true, "retryAfter": 60, "requestId": "abc-123" } ``` Action: * wait `retryAfter` seconds before retrying * read `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` headers * avoid tight polling loops; prefer webhooks or the wait endpoint for long-running executions ## Current Rate Limit All `/v1/*` endpoints are currently limited to **100 requests per minute per IP**. ## Retry Guidance Use the `retryable` field to decide whether to retry. As a general rule: **Safe to retry** (`retryable: true`): * `RATE_LIMITED` -- wait `retryAfter` seconds first * `INTERNAL_ERROR` -- transient server failure, use exponential backoff * `AUTH_FAILED` -- transient database error * `RESOURCE_UNAVAILABLE` -- service temporarily down * Network timeouts while fetching status * Idempotent reads (`GET /v1/apps`, `GET /v1/runs/{id}`) **Do not retry unchanged** (`retryable: false`): * `VALIDATION_ERROR` -- fix the inputs first * `AUTH_MISSING_KEY` / `AUTH_INVALID_KEY` -- fix credentials * `FORBIDDEN` -- access denied, won't change on retry * `NOT_FOUND` -- wrong ID ## Support Checklist If you need to debug an issue quickly, capture: * `requestId` from the error response * `code` from the error response * request path and method * app ID or execution ID * timestamp # The Agent Creation Loop Source: https://docs.uselamina.ai/guides/agent-creation-loop The canonical plan, approve, execute, monitor, and deliver loop for creative agents using Lamina MCP v2. The safe default for an interactive agent is: ```text theme={null} credits + brand context → plan → clarify → show frozen plan and cost → explicit approval → execute within budget → status → deliver ``` Connect to `https://app.uselamina.ai/mcp/agent/v2`. Lamina can select and stitch apps, atomic models, and composition steps behind a seven-tool task-level contract. ## Guarantees `lamina_plan` stores an immutable plan but never dispatches creative generation. Execution requires the exact plan fingerprint, explicit user approval, and a positive `maxCredits`. `lamina_execute` enforces the approved credit ceiling. Its idempotency key makes an exact retry return the original run and rejects changed requests using the same key. An unknown estimate is `null`, not zero. Execute unknown-cost work only after the user sees the warning and approves `allowUnknownCost: true`. ## The Loop Call `lamina_credits`. For brand-sensitive requests, call `lamina_brand_context` and preserve the returned `brandProfileId`. Empty brand fields are unconfigured, not an invitation to invent them. Call `lamina_plan` with the brief, known asset URLs, modality/platform, and optional constraints. Use `preferredPath` only when the user has a real routing preference. If status is `needs_clarification`, ask `questions[]` and plan again. For an `awaiting_approval` plan, collect `requiredInputs[]` answers for execution without rebuilding the plan. Show the stored steps, expected outputs, warnings, and estimated credits. Obtain explicit approval for both the frozen plan and the maximum spend. Call `lamina_execute` with the unchanged `planId`, `planFingerprint`, required `inputs`, finite `maxCredits`, and an idempotency key. Call `lamina_status` with the returned run ID. A waiting call is capped at 25 seconds; repeat it when `timedOut: true` or the snapshot is still non-terminal. On completion, return durable URLs from `outputs[]`. If the user stops the work, call `lamina_cancel` and report its actual state rather than claiming a request was already cancelled. ## Multi-Step Workflows Describe the full outcome in one plan: ```json theme={null} { "tool": "lamina_plan", "arguments": { "brief": "Make a branded product hero image, then animate it into a five-second vertical clip.", "modality": "video", "platform": "instagram-reels", "inputs": { "productImage": "https://example.com/product.png" }, "maxEstimatedCredits": 150 } } ``` Lamina freezes cross-step references in the plan and resolves them server-side. The agent should not copy an earlier output URL into a later step or rediscover tools after approval. ## Local Assets Call `lamina_upload_asset` to issue an `uploadUrl` and `assetUrl`. PUT the bytes to the signed URL before using `assetUrl`. If the agent environment cannot upload bytes, ask for a public URL. ## Normalized Run Lifecycle The v2 `lamina_status` and `lamina_cancel` tools work across: * v2 pipeline runs * advanced app-workflow runs * atomic image and video runs * narrated-video compose runs Preserve every returned `runId` exactly. Completed v2 status entries use `outputs[].url`. ## Advanced Post-Processing The advanced endpoint at `https://app.uselamina.ai/mcp/agent` exposes the full 30-tool surface. Use it only when the user explicitly needs a lower-level action such as: * `lamina_brand_score` for a completed image-producing workflow, atomic, or pipeline run * `lamina_brand_feedback` to record approve/reject feedback on a workflow run * `lamina_refine_to_brand` for bounded refinement of a rerunnable workflow image * `lamina_topup` to create a Stripe-hosted checkout URL Brand scoring does not support video-only, compose-only, or output-less runs. Refinement does not support arbitrary atomic, compose, or pipeline runs. Never collect card details in chat. ## REST and Headless Workers The `/v1` REST creation API remains suitable for server-owned integrations and unattended workers. Its documented headless creation, webhook, and `outputs[].value` contracts are separate from v2 MCP. Do not mix REST response shapes with v2's normalized MCP status. ## Minimal Interactive Run ```text theme={null} 1. lamina_credits 2. lamina_brand_context (when relevant) 3. lamina_plan { brief, inputs, maxEstimatedCredits } 4. ask questions / collect required inputs 5. show frozen plan + estimate → user approves 6. lamina_execute { planId, planFingerprint, inputs, maxCredits, idempotencyKey } 7. lamina_status { runId, wait: true, timeoutSeconds: 25 } 8. repeat status if needed 9. deliver outputs[].url ``` See [Agent Integration Patterns](/guides/agent-integration-patterns) for endpoint selection and [Agent Recipes](/guides/agent-recipes) for detailed examples. # Agent Integration Patterns Source: https://docs.uselamina.ai/guides/agent-integration-patterns How coding agents should plan, approve, execute, and monitor Lamina creative work through MCP v2. ## Choose the Right MCP Surface Lamina has two hosted OAuth MCP endpoints: | Endpoint | Use it for | Tool count | | --------------------------------------- | --------------------------------------------------------------------------------------------- | ---------- | | `https://app.uselamina.ai/mcp/agent/v2` | Outcome-level creative tasks, safe multi-step pipelines, and normal agent use | 7 | | `https://app.uselamina.ai/mcp/agent` | Direct app/model control, composition, brand mutations, generated-app management, or checkout | 30 | Start with v2. It gives the agent a small task-level contract and lets Lamina select and stitch creative capabilities into a frozen plan. Move to the advanced surface only when the request requires a lower-level operation that v2 does not expose. Both endpoints share one authorization server, but each advertises its own OAuth resource matching the URL you connect to (as RFC 9728 requires): ```text theme={null} https://app.uselamina.ai/mcp/agent/v2 ← resource for the v2 front door https://app.uselamina.ai/mcp/agent ← resource for the v1 advanced endpoint ``` So a client connecting to `/mcp/agent/v2` discovers metadata at `/.well-known/oauth-protected-resource/mcp/agent/v2`. Migrating v1 → v2 needs no re-authorization: tokens minted for the legacy `/mcp/agent` resource are cross-accepted at the v2 endpoint. ## Connect Install page: ```text theme={null} https://app.uselamina.ai/mcp/install ``` Recommended hosted endpoint: ```text theme={null} https://app.uselamina.ai/mcp/agent/v2 ``` For a remote MCP client: ```json theme={null} { "mcpServers": { "lamina": { "url": "https://app.uselamina.ai/mcp/agent/v2" } } } ``` For a client that only supports local stdio servers, bridge to the hosted OAuth server: ```json theme={null} { "mcpServers": { "lamina": { "command": "npx", "args": ["-y", "mcp-remote", "https://app.uselamina.ai/mcp/agent/v2"] } } } ``` `mcp-remote` opens the hosted OAuth flow in a browser. Do not add an API key to the configuration. The retired `@uselamina/mcp` package is not a supported local server. ## The Seven v2 Tools | Tool | Purpose | | ---------------------- | --------------------------------------------------------------------------------------------- | | `lamina_plan` | Turn a brief into a stored, costed, immutable pipeline without dispatching generation | | `lamina_execute` | Execute the exact approved plan with a hard credit ceiling and idempotency key | | `lamina_status` | Read or wait for pipeline, workflow, atomic, or compose runs through one normalized lifecycle | | `lamina_cancel` | Request idempotent cancellation for any supported run family | | `lamina_upload_asset` | Issue a signed URL for uploading an image, video, or audio input | | `lamina_brand_context` | Read workspace brand context and return the profile ID to use during planning | | `lamina_credits` | Read the workspace credit balance before paid work | ## Recommended Agent Flow Call `lamina_credits` before expensive work. If the request is brand-sensitive, call `lamina_brand_context` and pass its `brandProfileId` into planning. Call `lamina_plan` with the brief, known platform/modality, asset URLs, and any maximum estimated cost. Planning may use a paid router, but it does not dispatch creative generation. If the plan status is `needs_clarification`, ask `questions[]` and plan again. If it is `awaiting_approval`, collect answers for `requiredInputs[]` and keep them for execution without re-planning. Show the frozen steps, outputs, warnings, and estimated credits. Obtain explicit user approval for the plan and budget. Call `lamina_execute` with the unchanged `planId`, `planFingerprint`, collected `inputs`, a finite positive `maxCredits`, and an `idempotencyKey`. Call `lamina_status` with the returned `runId`. Use `wait=true` and an optional `timeoutSeconds` of at most 25. If the response is still non-terminal or reports `timedOut: true`, call status again with the same run ID. Return completed entries from `outputs[]`, using each output's `url`, media type, and label. Never claim success from a queued or running snapshot. ## Plan and Execute Contract Example plan request: ```json theme={null} { "tool": "lamina_plan", "arguments": { "brief": "Create a branded product hero image and a matching five-second launch clip.", "platform": "instagram", "brandProfileId": "", "inputs": { "productImage": "https://example.com/shoe.png" }, "maxEstimatedCredits": 120 } } ``` A successful plan returns a stored `planId`, `planFingerprint`, steps, output descriptions, required inputs, warnings, expiry, and `estimatedCredits`. A `null` estimate means unknown, not free. Execution must preserve the approved plan: ```json theme={null} { "tool": "lamina_execute", "arguments": { "planId": "", "planFingerprint": "", "inputs": { "productImage": "https://example.com/shoe.png" }, "maxCredits": 120, "idempotencyKey": "launch-shoe-v1" } } ``` Set `allowUnknownCost: true` only after displaying the unknown-cost warning and receiving approval. Reuse an idempotency key only for an exact retry; change it when the plan, inputs, or budget changes. ## Uploading Local Assets `lamina_upload_asset` issues an upload URL; it does not transfer the file bytes: ```json theme={null} { "tool": "lamina_upload_asset", "arguments": { "filename": "shoe.png", "mediaType": "image" } } ``` PUT the bytes to the returned `uploadUrl` with the required content type, then use `assetUrl` as a plan input. If the agent host cannot upload bytes, ask the user for a public asset URL. ## Status and Cancellation Always pass `runId` back exactly as returned. The v2 status tool normalizes pipeline, app-workflow, atomic image/video, and compose runs into the same lifecycle and returns completed assets in `outputs[]`. For **voiceover** runs, a completed status also carries `details.legibility` — a word-timed transcript plus measured delivery (words-per-minute, pace arc, pauses, emphasised words, and per-word loudness: dynamic range, loudest/softest words, and strong-emphasis words that are both drawn-out and loud) and the voice direction that was requested. Use it to evaluate whether the audio is on-brand and to give feedback, since the audio itself cannot be heard. Cancellation is provider-dependent. `cancel_requested` means Lamina accepted the request but may need the active provider operation to finish before stopping the next step. `not_cancellable` and `cancel_requested` must not be reported as confirmed `cancelled`. ## When to Use the Advanced Endpoint Connect separately to `https://app.uselamina.ai/mcp/agent` when the user needs: * direct app discovery, description, or app execution * direct atomic image/video model selection and generation * narrated-video composition controls * brand score, feedback, refinement, or brand-kit mutation * generated-app versioning, feedback, or visibility changes * a Stripe-hosted credit top-up link The advanced endpoint exposes 30 tools. Its `lamina_create` tool is **plan-only**: ```text theme={null} lamina_create → ask askUser[] questions → lamina_run ``` It never dispatches generation itself. Use `lamina_run` for a selected app, and use the universal `lamina_status` and `lamina_cancel` tools for the returned run ID. ## REST Integrations MCP uses `lamina_status` for delivery. Server-side integrations against `/v1` can instead attach a signed webhook: ```text theme={null} POST /v1/apps/{appId}/runs?webhook=https://your-agent.com/lamina-callback ``` For direct REST app runs, fetch current app metadata first and use the exact parameter keys and option labels it returns. REST workflow outputs use their documented `value` field; v2 MCP status normalizes completed artifact links as `outputs[].url`. ## Reliable Agent Behaviors * plan before spending and obtain explicit approval * preserve the plan fingerprint and run ID exactly * keep a finite credit ceiling * retry timed-out status waits instead of redispatching work * upload bytes before using a signed asset URL * treat unknown cost as unknown * surface typed failures and cancellation states truthfully Avoid guessing app IDs, model IDs, parameter names, option labels, brand facts, output URLs, or required assets. # Agent Recipes Source: https://docs.uselamina.ai/guides/agent-recipes End-to-end recipes for agents that plan, approve, execute, monitor, cancel, and reuse creative work with Lamina MCP v2. ## What Agents Should Prefer Interactive agents should start with: ```text theme={null} https://app.uselamina.ai/mcp/agent/v2 ``` The v2 surface has seven task-level tools and can stitch apps, atomic models, and composition into a single stored plan. Use the 30-tool advanced endpoint at `https://app.uselamina.ai/mcp/agent` only for direct control or operations v2 does not expose. ## Recipe 1: Plan, Approve, Execute Call `lamina_credits` and surface the current balance before paid generation. Call `lamina_plan` with the creative brief and known inputs. Planning does not dispatch generation. If `status` is `needs_clarification`, ask every returned question and call `lamina_plan` again with the clarified brief or inputs. For `awaiting_approval`, collect `requiredInputs[]` answers for execution without changing the frozen plan. Show the plan's steps, outputs, warnings, and estimated credits. Ask the user to approve both the work and a finite maximum credit budget. Call `lamina_execute` with the exact `planId` and `planFingerprint`, the collected inputs, `maxCredits`, and an idempotency key. Call `lamina_status` with the returned `runId` and `wait=true`. Repeat status when the run remains non-terminal or the wait reports `timedOut: true`. Return completed `outputs[]` URLs and their media types. Never treat `queued` or `running` as success. Example plan call: ```json theme={null} { "tool": "lamina_plan", "arguments": { "brief": "Create a cinematic Instagram launch image for a premium running shoe.", "platform": "instagram", "modality": "image", "inputs": { "productImage": "https://example.com/shoe.png" }, "maxEstimatedCredits": 80 } } ``` Illustrative approval-ready response: ```json theme={null} { "status": "awaiting_approval", "planId": "99ebc7c6-24f3-4fc2-9d53-2e3f49f3bec1", "planFingerprint": "", "steps": [ { "id": "hero", "kind": "app", "summary": "Generate the product hero image" } ], "requiredInputs": [], "estimatedCredits": 60, "warnings": [] } ``` After explicit approval: ```json theme={null} { "tool": "lamina_execute", "arguments": { "planId": "99ebc7c6-24f3-4fc2-9d53-2e3f49f3bec1", "planFingerprint": "", "inputs": { "productImage": "https://example.com/shoe.png" }, "maxCredits": 80, "idempotencyKey": "shoe-launch-image-v1" } } ``` ## Recipe 2: Clarification Without Plan Drift There are two kinds of questions: * `questions[]` with `status: "needs_clarification"` means the planner cannot freeze a safe plan. Ask the questions, then plan again with the answer. * `requiredInputs[]` with `status: "awaiting_approval"` belongs to the frozen plan. Collect those values and pass them to `lamina_execute`; do not call `lamina_plan` again. This distinction preserves the plan the user reviewed and approved. ## Recipe 3: Multi-Step Image and Video Ask Lamina to stitch the workflow rather than manually copying one step's output URL into the next: ```json theme={null} { "tool": "lamina_plan", "arguments": { "brief": "Create a 4:5 product hero image, then animate it into a five-second 9:16 launch clip.", "modality": "video", "platform": "instagram-reels", "inputs": { "productImage": "https://example.com/product.png" }, "preferredPath": "auto", "maxEstimatedCredits": 150 } } ``` The frozen plan contains the binding between steps. Preserve it through execution; do not rediscover apps or re-plan after approval. ## Recipe 4: Brand-Aware Planning Call brand context first: ```json theme={null} { "tool": "lamina_brand_context", "arguments": {} } ``` Use the returned `brandProfileId` in `lamina_plan`. Empty brand fields mean the workspace has not configured them—never invent voice, colors, guardrails, or performance claims. If the OAuth token lacks brand-read permission, planning continues without brand context and returns a warning. Reauthorize if brand grounding is required. ## Recipe 5: Upload a Local Asset Request a signed upload: ```json theme={null} { "tool": "lamina_upload_asset", "arguments": { "filename": "product-front.png", "mediaType": "image" } } ``` PUT the file bytes to `uploadUrl` using the returned content type. Only after the upload succeeds should the agent use `assetUrl` in planning or execution. The MCP tool does not upload bytes itself. ## Recipe 6: Poll and Cancel Reliably Status request: ```json theme={null} { "tool": "lamina_status", "arguments": { "runId": "", "wait": true, "timeoutSeconds": 25 } } ``` The same tool accepts pipeline, app-workflow, atomic image/video, and compose IDs. A wait that times out returns the latest snapshot; it does not mean the run failed. Cancellation request: ```json theme={null} { "tool": "lamina_cancel", "arguments": { "runId": "" } } ``` Cancellation is idempotent and provider-dependent. Report `cancel_requested` as a request, not as confirmed cancellation. A `not_cancellable` response is also not `cancelled`. ## Recipe 7: Use an Advanced App Directly Use the advanced endpoint only when the user requests direct app control: ```text theme={null} lamina_discover → lamina_describe → approval → lamina_run → lamina_status ``` Key `inputs` by the stable parameter keys returned from `lamina_describe`. Use only returned option labels. The advanced `lamina_create` tool is a compatibility name over the same frozen planner; legacy mode=`app` callers may still dispatch with `lamina_run`, but dynamic workflows, route choices, and bounded execution should use the v2 plan lifecycle. ## Recipe 8: SDK or REST Agent Server-side agents that own an API key can use `@uselamina/sdk` or `/v1`. Their output contract is the REST contract, where workflow output values are read from `outputs[].value`: ```ts theme={null} import { LaminaClient } from '@uselamina/sdk'; const client = LaminaClient.fromEnv(); const planned = await client.content.plan({ brief: 'Create a premium Instagram launch visual for a running shoe.', platform: 'instagram', modality: 'image', }); if (planned.data.canonicalStatus !== 'awaiting_approval') { console.log(planned.data.questions, planned.data.alternatives); process.exit(0); } const started = await client.content.execute(planned.data.planId, { planFingerprint: planned.data.planFingerprint, inputs: {}, maxCredits: planned.data.estimatedCredits ?? 25, allowUnknownCost: planned.data.estimatedCredits == null, idempotencyKey: 'launch-visual-001', }); const result = await client.content.status(started.data.runId); console.log(result.data.status, result.data.result); ``` Do not mix REST `outputs[].value` examples with v2 MCP status, which returns normalized `outputs[].url`. ## Smoke Test Checklist 1. Install `https://app.uselamina.ai/mcp/agent/v2`. 2. Complete OAuth and choose a workspace. 3. Confirm the client lists the task-level plan/choose/execute/status lifecycle tools. 4. Call `lamina_credits`. 5. Call `lamina_plan` with a simple image brief. 6. Resolve `questions[]` or `requiredInputs[]` without guessing. 7. Display the frozen plan and estimated cost, then obtain approval. 8. Call `lamina_execute` with a budget and idempotency key. 9. Call `lamina_status` until terminal. 10. Confirm completed outputs include usable URLs. 11. Exercise `lamina_cancel` on a test run where safe. ## Troubleshooting * `authorization_required`: start OAuth from the MCP client or reinstall the hosted server. * `insufficient_scope`: reconnect and approve the required scope. * `needs_clarification`: ask the returned planning questions and plan again. * `plan_expired`: build and approve a new plan. * `plan_fingerprint_mismatch`: execute the exact stored fingerprint; do not alter it. * `unknown_cost_requires_approval`: display the warning and request explicit permission before setting `allowUnknownCost: true`. * status wait timed out: call `lamina_status` again with the same run ID. * terminal failure: surface the returned code and message; do not silently switch apps or models. # Agent Runtime Benchmarks Source: https://docs.uselamina.ai/guides/agent-runtime-benchmarks Metrics, scenarios, and pass criteria for measuring Lamina as an agent-native creative runtime. Use this guide to measure whether an AI agent can install Lamina, authenticate, create assets, and retrieve outputs without custom recovery code. ## What To Measure Track every agent run against five stages: | Stage | Success Signal | Failure Stage | | ------- | ------------------------------------------------------------------------------------- | ------------- | | Install | Client discovers `/mcp/agent/v2` and completes dynamic registration or manual install | `install` | | Auth | OAuth authorization and token exchange succeed for the selected workspace | `auth` | | Input | The agent supplies enough brief, brand, and asset context to start a run | `input` | | Runtime | Lamina queues and completes the selected creative workflow | `runtime` | | Output | The agent receives usable final assets or structured outputs | `output` | The hosted MCP runtime emits benchmark-oriented telemetry events when server telemetry is enabled with `POSTHOG_SERVER_API_KEY`. | Event | When It Fires | | -------------------------------------------- | ----------------------------------------------------------------- | | `agent_runtime.install.discovery_challenged` | A remote MCP client discovers that `/mcp/agent/v2` requires OAuth | | `agent_runtime.install.client_registered` | Dynamic MCP OAuth registration succeeds | | `agent_runtime.install.failed` | Dynamic registration fails | | `agent_runtime.auth.authorize_redirected` | The OAuth authorize request validates and redirects to consent | | `agent_runtime.auth.approved` | A signed-in user approves workspace access | | `agent_runtime.auth.token_issued` | Authorization code or refresh-token exchange succeeds | | `agent_runtime.auth.succeeded` | A bearer token is accepted for `/mcp/agent/v2` | | `agent_runtime.auth.failed` | OAuth authorization, token exchange, or bearer validation fails | | `agent_runtime.tool_call.completed` | An MCP tool call completes and is classified | Every `agent_runtime.tool_call.completed` event includes: ```json theme={null} { "tool_name": "lamina_plan", "success": true, "outcome": "success", "failure_stage": null, "failure_category": null, "duration_ms": 1420, "auth_mode": "oauth", "run_status": "awaiting_approval", "needs_input": false, "output_count": 0 } ``` ## Benchmark Scenarios Run these scenarios for each supported MCP client before calling the distribution ready. | Scenario | Required Proof | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | Hosted OAuth install | The client discovers metadata, registers or uses its configured client, shows consent, receives tokens, and lists the Lamina tools | | First image run | The agent plans from a short prompt, obtains approval, calls `lamina_execute`, and receives an image through `lamina_status` | | First video run | The agent plans and executes a video task, then waits or polls until a terminal result | | Brand-aware planning | The agent calls `lamina_brand_context`, passes its `brandProfileId` to `lamina_plan`, and preserves the same workspace context | | Multi-step pipeline | The agent plans an image-to-video task and Lamina executes the frozen cross-step bindings without the client copying intermediate URLs | | Clarification loop | An intentionally underspecified request returns `needs_clarification`; the agent asks `questions[]` and plans again | | Auth recovery | An expired or insufficient-scope token yields a clear OAuth error and the client can reauthorize | ## Pass Criteria Use these thresholds for the preferred-runtime scorecard: | Metric | Target | | ----------------------------------- | ----------------------------------------------------- | | Install success rate | 95% or higher per supported client | | First successful generation time | Under 5 minutes from clean client install | | Tool call auth failure rate | Under 1% after successful install | | Clarification-loop rate | Tracked separately from hard failures | | Run completion rate | 90% or higher for benchmark workflows | | Webhook or polling delivery success | 99% for terminal run visibility | | Output usability rate | 95% of completed runs have at least one usable output | Do not count `needs_clarification` as a runtime failure. It is a planning outcome and should be optimized by improving examples and prompt mapping. Likewise, required inputs on an `awaiting_approval` plan are not runtime failures. ## Suggested Benchmark Record Store one record per client, scenario, and run: ```json theme={null} { "client": "claude-code", "scenario": "first-image-run", "startedAt": "2026-04-23T07:00:00.000Z", "completedAt": "2026-04-23T07:02:11.000Z", "installSucceeded": true, "authSucceeded": true, "toolCalls": [ { "name": "lamina_plan", "outcome": "success", "durationMs": 812 }, { "name": "lamina_execute", "outcome": "success", "durationMs": 931 }, { "name": "lamina_status", "outcome": "success", "durationMs": 1304 } ], "runId": "00000000-0000-0000-0000-000000000000", "finalStatus": "completed", "outputCount": 1, "failureStage": null, "notes": "Clean install, OAuth consent, one image output." } ``` ## Dashboard Breakdown At minimum, build dashboard cards for: * Install starts, successful registrations, and failed registrations by MCP client * OAuth approvals, token issues, bearer-token failures, and insufficient-scope failures * Tool-call success rate by `tool_name` * `needs_clarification` and missing-required-input rates by requested modality * Runtime failure rate by workflow/app when available * Empty-output and failed-output rate after terminal `completed` status * Time from first install event to first completed output ## Related Guides * [MCP OAuth Install](/guides/mcp-oauth-install) * [Agent Integration Patterns](/guides/agent-integration-patterns) * [Handle Long-Running Executions](/guides/handle-long-running-executions) # Integration Recipes Source: https://docs.uselamina.ai/guides/capability-recipes Map common generative media use cases to the real Lamina `/v1` API for image, catalog, try-on, and video workflows. Use this guide when you want to turn Lamina into a product feature, not just call a raw model. Each recipe below uses the same execution pattern: 1. discover the right app 2. inspect its schema 3. start an execution 4. receive results by webhook or polling That makes the API predictable for application developers even when the underlying workflow changes. ## The Real API Behind Every Recipe The `/v1` API has 24 endpoints across 9 groups. The core execution flow uses: * `GET /v1/apps` — discover apps * `GET /v1/apps/{appId}` — inspect inputs * `POST /v1/apps/{appId}/runs` — run * `GET /v1/runs/{runId}` — poll results Additional groups — Intelligence, Publishing, Content, Templates, Assets, Account — extend what you can do after execution. See the full [API Reference](/api-reference/list-apps). ## Core Contract | Capability need | Current executable Lamina endpoint | | --------------------------------------------- | ------------------------------------ | | Start image, catalog, try-on, or video work | `POST /v1/apps/{appId}/runs` | | Check job status | `GET /v1/runs/{runId}` | | Stream job progress in real time | `GET /v1/runs/{runId}/stream` | | Discover which app to call | `GET /v1/apps` | | Verify the exact input schema | `GET /v1/apps/{appId}` | | Intelligent content creation (agent-friendly) | `POST /v1/content/create` | | Batch multiple creations | `POST /v1/content/batch` | | Get brand context for grounded prompts | `GET /v1/intelligence/brand-context` | | Publish to social channels | `POST /v1/publishing/publish` | | Check credit balance | `GET /v1/account/usage` | ### Authentication ```http theme={null} x-api-key: lma_your_api_key Authorization: Bearer lma_your_api_key ``` ### Rate Limits All `/v1/*` endpoints are currently limited to **100 requests per minute per IP**. On `429 Too Many Requests`, read these headers before retrying: * `RateLimit-Limit` * `RateLimit-Remaining` * `RateLimit-Reset` * `Retry-After` ### Common Status Codes | Status | Meaning | | ------ | -------------------------------------------- | | `200` | Successful read | | `202` | Execution accepted and queued | | `400` | Invalid request body, webhook URL, or inputs | | `401` | Missing or invalid API key | | `403` | Workspace or app access denied | | `404` | App or execution not found | | `429` | Rate limit exceeded | | `500` | Unexpected server-side failure | ## Example Public Apps By Capability These are example public apps observed on **April 11, 2026**. Use them as capability anchors in docs or demos, but pin the exact `appId` you want to support in production and re-check its schema with `GET /v1/apps/{appId}` before you ship against it. | Capability | Example public app | Example appId | | ------------------------ | ----------------------------------------- | -------------------------------------- | | Single image generation | `Product Shots with Mood Board` | `ec7ec3ce-69b4-43c9-8eea-fe9752d679a4` | | Batch catalog generation | `Premium Catalog 1.0` | `bbb17293-5fe7-4645-8c6e-0745bc28254d` | | Batch catalog generation | `Swift Catalog Generation` | `de5cca6b-73aa-4e27-a714-3339024db15d` | | Virtual try-on | `Product Try on` | `a76e85ec-20b4-4fbc-99ad-055423a868a2` | | Video generation | `Eyewear Shoot (Multi-Shot 21s Video)` | `1afc70fd-cb66-4f44-847d-bdaf7e237be4` | | Video generation | `Performance Marketing Video (V3) Sample` | `ec9b3525-4b72-4083-a6cb-00371672e128` | ## Ecommerce Image Generation Use this pattern for: * product shots * background swaps * lifestyle scenes * hero images **Recommended backing app example:** `Product Shots with Mood Board` ### Typical integration flow ```bash theme={null} curl -H "Authorization: Bearer $LAMINA_API_KEY" \ "https://app.uselamina.ai/v1/apps?search=product" ``` Then inspect the chosen app and use the returned parameter names exactly: ```bash theme={null} curl -H "Authorization: Bearer $LAMINA_API_KEY" \ https://app.uselamina.ai/v1/apps/ ``` ```bash theme={null} curl -X POST \ -H "Authorization: Bearer $LAMINA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "inputs": { "": "" } }' \ https://app.uselamina.ai/v1/apps//runs ``` ### What developers usually send * a product image URL or a set of source images * optional reference imagery * a prompt or creative brief * styling or environment choices exposed as `options` ### What to expect back * one or more generated image outputs * occasional text outputs for summaries or metadata * `queued` or `running` before final media is ready ## Catalog And Merchandising Automation Catalog workflows are usually run by commerce backends, merchant tooling, or creative ops teams that need consistent outputs across many products. **Recommended backing app examples:** * `Premium Catalog 1.0` * `Swift Catalog Generation` For a concrete catalog payload, the existing Quick Start example uses: * front image URL * back image URL * model or style options such as gender, ethnicity, body type, and location Example execution body: ```json theme={null} { "inputs": { "Front": "https://example.com/front.jpg", "Back": "https://example.com/back.jpg", "Model Gender": "Female", "Location": "Studio" } } ``` Send it to: ```http theme={null} POST /v1/apps/{catalogAppId}/runs ``` ### Good fit for * marketplace seller onboarding * catalog enrichment pipelines * merchandising teams refreshing seasonal collections * internal creative production systems ## Virtual Try-On Try-on integrations usually combine shopper-uploaded imagery, mannequin imagery, or model imagery with garment assets supplied by a commerce system. **Recommended backing app example:** `Product Try on` Suggested discovery query: ```bash theme={null} curl -H "Authorization: Bearer $LAMINA_API_KEY" \ "https://app.uselamina.ai/v1/apps?search=try on" ``` Expected input pattern: * one or more person or selfie image URLs * one or more garment image URLs * optional fit, pose, or styling fields depending on the app Execution call: ```bash theme={null} curl -X POST \ -H "Authorization: Bearer $LAMINA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "inputs": { "": "https://example.com/person.jpg", "": "https://example.com/garment.jpg" } }' \ https://app.uselamina.ai/v1/apps//runs ``` ### Common platform pattern * fetch the app schema at startup or cache it server-side * render the form dynamically for internal tooling or merchant portals * pass the resulting execution ID into your job UI * use webhooks for completion updates instead of aggressive polling ## Video Generation Video generation follows the same app-execution pattern, but jobs are more likely to be long-running and media-heavy. This is a good fit for product marketing tools, seller studios, and content platforms generating motion assets from still inputs. **Recommended backing app examples:** * `Eyewear Shoot (Multi-Shot 21s Video)` * `Performance Marketing Video (V3) Sample` Suggested discovery query: ```bash theme={null} curl -H "Authorization: Bearer $LAMINA_API_KEY" \ "https://app.uselamina.ai/v1/apps?search=video" ``` Common input pattern: * one or more source image URLs * optional product or motion brief text * optional aspect ratio, duration, or style fields from app metadata Execution call: ```bash theme={null} curl -X POST \ -H "Authorization: Bearer $LAMINA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "inputs": { "": "https://example.com/product.jpg", "": "Create a premium ecommerce product reel" } }' \ "https://app.uselamina.ai/v1/apps//runs?webhook=https://your-server.com/callback" ``` Use webhooks in production for video jobs. They are often long-running enough that polling is only a fallback. ## Job Status And Result Retrieval Every capability above resolves to the same status check: ```http theme={null} GET /v1/runs/{runId} ``` Use the run ID returned from `POST /v1/apps/{appId}/runs`. Poll every 3-5 seconds until the run reaches `completed` or `failed`. If you are using webhooks, verify the callback signature with: ```http theme={null} GET /v1/webhooks/signing-key ``` ## Production Guidelines If you are building a customer-facing integration or partner API on top of Lamina: 1. pin the `appId` values you support instead of relying on search at runtime 2. re-fetch app metadata when you deploy changes to a workflow or form 3. validate inputs against the latest parameter schema before execution 4. prefer webhooks for long-running image and video jobs 5. design your UI around execution states, not immediate outputs The underlying API stays small on purpose. Most of the product-specific behavior lives in the apps you choose to expose. # Handle Long-Running Executions Source: https://docs.uselamina.ai/guides/handle-long-running-executions Recommended patterns for apps that take minutes to complete, especially image-heavy and video-heavy workflows. ## Why Lamina Uses Async Jobs Some Lamina apps complete in seconds. Others, especially multi-step image/video pipelines, may take much longer. Because of that, `POST /v1/apps/{appId}/runs` starts work and returns an execution handle immediately instead of blocking until the final output is ready. If you are integrating Lamina into a backend, queue system, merchant tool, or content platform, design around the execution lifecycle from the start. ## Two Delivery Patterns ### Option 1: Webhook (Recommended) Pass a webhook URL as a query parameter. When the execution completes, we POST the results to your URL. ```bash theme={null} POST /v1/apps/{appId}/runs?webhook=https://your-server.com/callback ``` Benefits: * No polling loop needed * Results arrive as soon as they're ready * Signed with ED25519 for security * Automatic retries (3 attempts with backoff) ### Option 2: Polling Poll `GET /v1/runs/{runId}` on an interval until `status` reaches `completed` or `failed`. Use different polling intervals depending on the workflow: * Short image jobs: every 3-5 seconds * Heavier multi-step jobs: every 5-10 seconds * Long-running video jobs: every 10-15 seconds Avoid polling every second in production. ## Recommended Backend Flow Call `POST /v1/apps/{appId}/runs?webhook=` and store the returned run ID. Save it in your job table, queue, or request state so you can resume later. Your webhook endpoint receives the completed results, or poll `GET /v1/runs/{runId}` as a fallback. ## What To Persist For every execution you start, store at least: * `runId` * `appId` * input payload * current status * started timestamp This makes retries, dashboards, and support much easier. ## Failure Handling If an execution fails: * inspect the top-level `errorMessage` * inspect each output's `error` * keep the original inputs for debugging or replay If your product has end users, show a user-friendly status while keeping the raw error for logs and support tooling. ## Webhook Verification When receiving webhook callbacks, verify the signature to ensure it's from Lamina: 1. Fetch the public key from `GET /v1/webhooks/signing-key` 2. Verify the ED25519 signature: `verify(signature, ".", publicKey)` 3. Reject timestamps older than 5 minutes (replay protection) See the [Webhook Signing Key](/api-reference/get-webhook-signing-key) reference for verification code examples. # MCP OAuth Install Source: https://docs.uselamina.ai/guides/mcp-oauth-install Install Lamina MCP v2 as a hosted remote server with OAuth and workspace-scoped access. ## Recommended Endpoint Connect normal agent workflows to: ```text theme={null} https://app.uselamina.ai/mcp/agent/v2 ``` It exposes seven task-level tools for planning, approval, execution, status, cancellation, asset upload, brand context, and credits. The lower-level 30-tool endpoint is: ```text theme={null} https://app.uselamina.ai/mcp/agent ``` Use the advanced endpoint only when the agent needs direct app/model control, composition, brand or app mutations, refinement, or checkout. The hosted install page provides copyable values: ```text theme={null} https://app.uselamina.ai/mcp/install ``` ## Install Pattern Use a remote Streamable HTTP MCP configuration when the client supports it: ```json theme={null} { "mcpServers": { "lamina": { "url": "https://app.uselamina.ai/mcp/agent/v2" } } } ``` The client discovers Lamina OAuth, opens browser consent, lets the user choose a workspace, and stores a workspace-scoped access token. Do not paste an API key into tool arguments. ### add-mcp For clients supported by the community installer: ```bash theme={null} npx add-mcp https://app.uselamina.ai/mcp/agent/v2 ``` Add `-g` for a global install: ```bash theme={null} npx add-mcp -g https://app.uselamina.ai/mcp/agent/v2 ``` ### Codex CLI ```bash theme={null} codex mcp add lamina --url https://app.uselamina.ai/mcp/agent/v2 codex mcp list ``` Equivalent `~/.codex/config.toml`: ```toml theme={null} [mcp_servers.lamina] url = "https://app.uselamina.ai/mcp/agent/v2" ``` ### Claude Code ```bash theme={null} claude mcp add --transport http lamina --scope user \ https://app.uselamina.ai/mcp/agent/v2 ``` Run `/mcp` in Claude Code to authenticate, then verify with: ```bash theme={null} claude mcp list claude mcp get lamina ``` ### Cursor Use `.cursor/mcp.json` or the global MCP configuration: ```json theme={null} { "mcpServers": { "lamina": { "url": "https://app.uselamina.ai/mcp/agent/v2" } } } ``` Refresh MCP servers and complete OAuth when prompted. ### VS Code Create `.vscode/mcp.json`: ```json theme={null} { "servers": { "lamina": { "type": "http", "url": "https://app.uselamina.ai/mcp/agent/v2" } } } ``` Start or restart the server from the MCP server list, complete OAuth, and enable the seven Lamina tools in agent mode. ### Other Remote MCP Clients Use the same v2 URL as a custom Streamable HTTP or remote MCP server: ```text theme={null} https://app.uselamina.ai/mcp/agent/v2 ``` Configuration field names vary (`url`, `serverUrl`, `httpUrl`, or endpoint URL), but authentication must use the hosted OAuth flow. ## Stdio-Only Clients If a client can only launch a local process, bridge stdio to Lamina's hosted server: ```bash theme={null} npx -y mcp-remote https://app.uselamina.ai/mcp/agent/v2 ``` Example configuration: ```json theme={null} { "mcpServers": { "lamina": { "command": "npx", "args": ["-y", "mcp-remote", "https://app.uselamina.ai/mcp/agent/v2"] } } } ``` `mcp-remote` opens Lamina OAuth in a browser and forwards the hosted tools. Do not add `LAMINA_API_KEY` for this bridge. The self-hosted `@uselamina/mcp` npm package is retired. Connect to the hosted v2 endpoint directly or through `mcp-remote`. ## OAuth Discovery Each MCP endpoint publishes protected-resource metadata whose `resource` **matches the URL you connect to** — as RFC 9728 requires. A spec-correct client verifies that the metadata's `resource` equals the server it is protecting and aborts on a mismatch, so v2 and v1 each advertise their own identifier: ```text theme={null} # v2 front door (recommended) GET /.well-known/oauth-protected-resource/mcp/agent/v2 → resource: …/mcp/agent/v2 # v1 advanced endpoint GET /.well-known/oauth-protected-resource/mcp/agent → resource: …/mcp/agent # shared authorization server GET /.well-known/oauth-authorization-server ``` Connecting to `/mcp/agent/v2` without a token returns a bearer challenge pointing at the v2 metadata: ```text theme={null} WWW-Authenticate: Bearer resource_metadata="https://app.uselamina.ai/.well-known/oauth-protected-resource/mcp/agent/v2" ``` The v2 protected-resource response: ```json theme={null} { "resource": "https://app.uselamina.ai/mcp/agent/v2", "authorization_servers": ["https://app.uselamina.ai"], "bearer_methods_supported": ["header"], "scopes_supported": ["lamina:creative:read", "lamina:creative:write", "lamina:brand:read"] } ``` Use the resource indicator that matches the endpoint you connect to (`/mcp/agent/v2` for the front door). You do **not** need to re-authorize when migrating v1 → v2: tokens minted for the legacy `/mcp/agent` resource are cross-accepted at the v2 endpoint, and both endpoints share one authorization server, so an existing grant keeps working. ## Authorization Flow Dynamic clients call `POST /mcp/oauth/register` with their name and redirect URIs. Public clients use `token_endpoint_auth_method: "none"`. Open `/mcp/oauth/authorize` with the standard authorization-code fields, the `resource` matching the endpoint you connect to (`https://app.uselamina.ai/mcp/agent/v2` for the front door), and a PKCE `S256` challenge. The user chooses the Lamina workspace and approves the requested scopes. POST the code and PKCE verifier to `/mcp/oauth/token`, again using the same `resource`. Send MCP requests to `/mcp/agent/v2` with `Authorization: Bearer <access_token>`. POST the token to `/mcp/oauth/revoke` when the user removes Lamina. ## Scopes * `lamina:creative:read` allows credits and run status. * `lamina:creative:write` allows planning, execution, cancellation, and signed uploads. * `lamina:brand:read` allows reading brand context. If a token lacks a tool's required scope, Lamina returns an insufficient-scope bearer challenge so the client can request step-up authorization. ## Verify the Install 1. Confirm the client lists exactly: `lamina_plan`, `lamina_execute`, `lamina_status`, `lamina_cancel`, `lamina_upload_asset`, `lamina_brand_context`, and `lamina_credits`. 2. Call `lamina_credits`. 3. Call `lamina_plan` with a small test brief. 4. Confirm planning returns a plan or clarification without dispatching generation. 5. Display the plan and estimated cost before testing `lamina_execute`. Example plan: ```json theme={null} { "tool": "lamina_plan", "arguments": { "brief": "Create a cinematic product hero image for a running shoe.", "platform": "instagram", "modality": "image", "maxEstimatedCredits": 80 } } ``` After explicit approval: ```json theme={null} { "tool": "lamina_execute", "arguments": { "planId": "", "planFingerprint": "", "inputs": {}, "maxCredits": 80, "idempotencyKey": "mcp-install-smoke-test-v1" } } ``` Poll the returned run: ```json theme={null} { "tool": "lamina_status", "arguments": { "runId": "", "wait": true, "timeoutSeconds": 25 } } ``` If status reports `timedOut: true` or remains non-terminal, call it again with the same run ID. Completed artifacts are returned in normalized `outputs[]` entries with durable `url` values. ## Revoke Access ```http theme={null} POST /mcp/oauth/revoke Content-Type: application/json { "client_id": "lamina_mcp_client_...", "token": "lma_mcp_at_or_rt_...", "token_type_hint": "refresh_token" } ``` Revoking either token revokes the stored token pair for that grant. A later MCP call with the bearer token returns `invalid_token`. ## Troubleshooting * The client shows 30 tools: it is connected to `/mcp/agent`; switch normal workflows to `/mcp/agent/v2`. * The client shows no tools: inspect the OAuth prompt and server logs, then reconnect. * `invalid_token`: revoke/reconnect and complete OAuth again. * `insufficient_scope`: reconnect and approve the requested scope. * The client only supports stdio: use `mcp-remote` with the v2 URL. * Planning works but execution fails: verify the exact fingerprint, required inputs, positive credit ceiling, and idempotency key. # Run Your First App Source: https://docs.uselamina.ai/guides/run-your-first-app A practical end-to-end flow: discover an app, inspect its inputs, execute it, and get the result. This is the simplest complete Lamina flow. It uses only the existing `/v1` endpoints: * `GET /v1/apps` * `GET /v1/apps/{appId}` * `POST /v1/apps/{appId}/runs` * `GET /v1/runs/{runId}` ## Step 1: List Available Apps ```bash theme={null} curl -H "x-api-key: lma_your_api_key" \ https://app.uselamina.ai/v1/apps ``` Look through the response and choose an `appId`. ## Step 2: Inspect The App ```bash theme={null} curl -H "x-api-key: lma_your_api_key" \ https://app.uselamina.ai/v1/apps/{appId} ``` Use this response to determine: * which fields are required * which parameters are `text`, `options`, or `url` * which default values are already defined ## Step 3: Start Execution ### With webhook ```bash theme={null} curl -X POST \ -H "x-api-key: lma_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "inputs": { "Prompt": "A cinematic ecommerce hero image", "Aspect Ratio": "16:9" } }' \ "https://app.uselamina.ai/v1/apps/{appId}/runs?webhook=https://your-server.com/callback" ``` Use this in production when you want Lamina to notify your backend as soon as the job finishes. ### Without webhook ```bash theme={null} curl -X POST \ -H "x-api-key: lma_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "inputs": { "Prompt": "A cinematic ecommerce hero image", "Aspect Ratio": "16:9" } }' \ https://app.uselamina.ai/v1/apps/{appId}/runs ``` The response includes a run ID. ## Step 4: Get Results ### Via webhook If you passed `?webhook=`, your callback URL receives a POST when the execution completes: ```json theme={null} { "data": { "runId": "fc32ae7d-...", "status": "completed", "outputs": [ { "id": "aiDesignerNode-...", "label": "Hero Image", "type": "image", "value": "https://storage.example.com/generated.png", "status": "completed", "error": null } ] } } ``` Headers include `X-Lamina-Webhook-Signature` for verification. See [Webhook Signing Key](/api-reference/get-webhook-signing-key) for details. ### Via polling If not using webhooks, poll until the execution reaches a terminal state: ```bash theme={null} curl -H "x-api-key: lma_your_api_key" \ https://app.uselamina.ai/v1/runs/{runId} ``` Poll every 3-5 seconds. Stop when `status` is `completed` or `failed`. ## JavaScript Example ```js theme={null} const baseUrl = 'https://app.uselamina.ai'; const apiKey = process.env.LAMINA_API_KEY; async function laminaFetch(path, init = {}) { const response = await fetch(`${baseUrl}${path}`, { ...init, headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, ...(init.headers || {}), }, }); if (!response.ok) { const body = await response.text(); throw new Error(`Lamina request failed: ${response.status} ${body}`); } return response.json(); } // With webhook — start and forget (results arrive at your webhook) async function runAppWithWebhook(appId, inputs, webhookUrl) { const started = await laminaFetch( `/v1/apps/${appId}/runs?webhook=${encodeURIComponent(webhookUrl)}`, { method: 'POST', body: JSON.stringify({ inputs }) } ); return started.data.runId; } // With polling — start and wait async function runAppWithPolling(appId, inputs) { const started = await laminaFetch(`/v1/apps/${appId}/runs`, { method: 'POST', body: JSON.stringify({ inputs }), }); const runId = started.data.runId; for (;;) { const status = await laminaFetch(`/v1/runs/${runId}`); const state = status.data.status; if (state === 'completed' || state === 'failed') { return status.data; } await new Promise((resolve) => setTimeout(resolve, 5000)); } } ``` ## Python Example ```python theme={null} import os import time import requests from urllib.parse import urlencode BASE_URL = "https://app.uselamina.ai" API_KEY = os.environ["LAMINA_API_KEY"] headers = { "x-api-key": API_KEY, "Content-Type": "application/json", } # With webhook — start and forget def run_app_with_webhook(app_id, inputs, webhook_url): r = requests.post( f"{BASE_URL}/v1/apps/{app_id}/runs?webhook={webhook_url}", headers=headers, json={"inputs": inputs}, timeout=60, ) r.raise_for_status() return r.json()["data"]["runId"] # With polling — start and wait def run_app_with_polling(app_id, inputs): started = requests.post( f"{BASE_URL}/v1/apps/{app_id}/runs", headers=headers, json={"inputs": inputs}, timeout=60, ) started.raise_for_status() run_id = started.json()["data"]["runId"] while True: status = requests.get( f"{BASE_URL}/v1/runs/{run_id}", headers=headers, timeout=60, ) status.raise_for_status() payload = status.json()["data"] if payload["status"] in ("completed", "failed"): return payload time.sleep(5) ``` # Test Webhooks Locally Source: https://docs.uselamina.ai/guides/test-webhooks-locally Receive real Lamina execution callbacks on your machine, verify signatures, and reuse the same flow from the CLI or an agent. ## Why This Matters Most production Lamina integrations should use webhooks instead of tight polling loops. The local CLI can now do the same thing: * start a local webhook receiver * verify Lamina's ED25519 signature * expose the listener through ngrok or another public tunnel * run a real app against that callback URL ## Lamina Webhook Contract When you start an execution with: ```http theme={null} POST /v1/apps/{appId}/runs?webhook=https://your-server.com/callback ``` Lamina sends a signed POST when the execution finishes. Headers: * `X-Lamina-Webhook-Signature` * `X-Lamina-Webhook-Timestamp` * `X-Lamina-Webhook-Request-Id` Message to verify: ```text theme={null} . ``` Verification key source: ```http theme={null} GET /v1/webhooks/signing-key ``` The CLI listener and MCP listener both verify this contract directly. ## Start A Local Listener From the repository root: ```bash theme={null} lamina webhook listen --port 8788 ``` That starts a local receiver at: ```text theme={null} http://127.0.0.1:8788/lamina/webhook ``` ## Expose It Publicly Use ngrok, cloudflared, or your preferred tunnel. Example with ngrok: ```bash theme={null} ngrok http 8788 ``` If the public URL is `https://example.ngrok.dev`, save it as the default Lamina webhook URL: ```bash theme={null} lamina webhook listen \ --port 8788 \ --public-url https://example.ngrok.dev \ --save-default ``` The CLI normalizes that to: ```text theme={null} https://example.ngrok.dev/lamina/webhook ``` Check what is saved: ```bash theme={null} lamina webhook status ``` Clear it if needed: ```bash theme={null} lamina webhook clear ``` ## Run An App Against The Saved Webhook URL ```bash theme={null} lamina run --file inputs.json --webhook default ``` You can also pass the public URL directly: ```bash theme={null} lamina run \ --file inputs.json \ --webhook https://example.ngrok.dev/lamina/webhook ``` ## Expected Listener Output For a successful callback, the listener prints a verified execution message like: ```text theme={null} Verified webhook 1 for execution 99ebc7c6-24f3-4fc2-9d53-2e3f49f3bec1 (completed) ``` If verification fails, the listener rejects the callback and prints the verification error instead. ## Real End-To-End Flow Run `lamina login` (browser OAuth) or `lamina login --api-key lma_...` for CI. Run `lamina webhook listen --port 8788`. Start ngrok or another tunnel and get a public HTTPS URL. Start the listener with `--public-url ... --save-default`, or pass the full webhook URL directly when you run the app. Execute `lamina run --file inputs.json --webhook default`. The local listener validates the Lamina signature and prints the completed execution. ## MCP Uses Polling Instead The hosted MCP server exposes creative, brand, and billing tools — but no webhook listener management tools. Result delivery over MCP is polling-based. For MCP clients, use the v2 `lamina_plan` → approval → `lamina_execute` flow and retrieve results with `lamina_status`. Use REST webhooks when you are building a server-side integration that owns a public callback URL. ## Fallback If you do not want to expose a callback during local development, omit `?webhook=` and use polling with: ```bash theme={null} lamina run --file inputs.json --wait ``` # Use The CLI And SDK Source: https://docs.uselamina.ai/guides/use-the-cli-and-sdk How to run Lamina from a terminal, save an API key, inspect apps, execute jobs, and understand where the SDK fits today. ## Choose The Right Integration Surface Lamina supports three developer entry points: | Surface | Best for | What it does | | ---------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------ | | HTTP API | Backends and production services | Call `/v1` directly from your own code | | CLI | Terminal workflows and manual testing | Save an API key, inspect apps, run executions, receive webhook callbacks | | MCP server | Coding agents such as Claude Code, Cursor, and desktop MCP clients | Expose Lamina as tools an agent can call | The HTTP API is the canonical contract. The CLI and MCP server are thin wrappers around that same `/v1` surface. ## Install From npm ```bash theme={null} npm install @uselamina/sdk npm install -g @uselamina/cli ``` For MCP, use the **hosted v2 server** at `https://app.uselamina.ai/mcp/agent/v2` (OAuth), or install it from [https://app.uselamina.ai/mcp/install](https://app.uselamina.ai/mcp/install). Use `https://app.uselamina.ai/mcp/agent` only for the 30-tool advanced surface. The old self-hosted `@uselamina/mcp` npm package is retired. ## Authenticate Once `lamina login` opens your browser for an OAuth approval flow — pick a workspace, click **Approve**, and the CLI receives the token on a loopback callback. ```bash theme={null} lamina login ``` For CI / scripted callers, pass a workspace API key non-interactively: ```bash theme={null} lamina login --api-key lma_your_api_key ``` Either path stores credentials at `~/.lamina/config.json` (mode `0600`). OAuth tokens auto-refresh near expiry — you stay logged in for up to 30 days without re-prompting. Inspect the active identity at any time: ```bash theme={null} lamina whoami ``` Or skip local storage entirely and use the environment: ```bash theme={null} export LAMINA_API_KEY=lma_your_api_key ``` The CLI resolves auth in this order: 1. `LAMINA_API_KEY` environment variable 2. saved CLI credentials at `~/.lamina/config.json` (OAuth tokens or API key) Sign out: ```bash theme={null} lamina logout ``` ## Inspect Apps List available apps: ```bash theme={null} lamina apps list --search catalog ``` Inspect one app's parameter contract: ```bash theme={null} lamina apps get ``` ## Upload A Local File Pass a path; the CLI streams the bytes to Lamina's CDN via a pre-signed URL and prints the resulting URL you can pass to `lamina run --input ...`: ```bash theme={null} URL=$(lamina assets upload ./me.jpg --json | jq -r '.data.url') ``` ## Run An App You can run with a JSON file: ```bash theme={null} lamina run --file inputs.json --wait ``` Or inline values: ```bash theme={null} lamina run \ --input "Your photo=https://example.com/selfie.png" \ --input "Celebrity Name=Anne Hathaway" \ --input "Aspect Ratio=1:1" \ --wait ``` The CLI fetches the app schema first and validates: * unknown parameter names * missing required inputs * invalid option labels * invalid `url` values ## Where The SDK Fits The shared Lamina client is published as `@uselamina/sdk` and powers the CLI. If you are integrating from your own backend, use `@uselamina/sdk` or call the `/v1` HTTP API directly. The client wraps: * `GET /v1/apps` * `GET /v1/apps/{appId}` * `GET /v1/apps/{appId}/workflow` * `POST /v1/apps/{appId}/runs` * `GET /v1/runs/{runId}` * `GET /v1/webhooks/signing-key` ## Next Steps * Read [Test Webhooks Locally](/guides/test-webhooks-locally) to receive signed Lamina callbacks on your machine * Read [Agent Integration Patterns](/guides/agent-integration-patterns) to run Lamina through MCP in coding agents * Read [Quick Start](/quick-start) if you want to stay on the raw HTTP API # Lamina Source: https://docs.uselamina.ai/introduction The generative media platform for AI agents. Create product images, videos, virtual try-on, and more through MCP, REST API, or CLI. The generative media platform for AI agents. You call apps — not models. Lamina handles orchestration, brand context, quality scoring, and multi-channel publishing.