---
title: "Generate mockups"
url: https://docs.light-ai.app/api-reference/mockups/create-mockup-generation.md
canonical_url: https://docs.light-ai.app/api-reference/mockups/create-mockup-generation
last_updated: 2026-10-13
summary: "POST /v1/projects/{project_id}/mockups: Starts a Light AI Gen 3.0 generation of 1–4 lighting mockups for the project, exactly as the app generates them: from the project's front view and its measured guide lines (default), or from a front photo (`photo_url`, fetched once, or `pho"
api_status: beta
related:
  - https://docs.light-ai.app/api-reference/mockups
---

# Generate mockups

> POST /v1/projects/{project_id}/mockups: Starts a Light AI Gen 3.0 generation of 1–4 lighting mockups for the project, exactly as the app generates them: from the project's front view and its measured guide lines (default), or from a front photo (`photo_url`, fetched once, or `pho

`POST /v1/projects/{project_id}/mockups`

Starts a Light AI Gen 3.0 generation of 1–4 lighting mockups for the project, exactly as the app generates them: from the project's front view and its measured guide lines (default), or from a front photo (`photo_url`, fetched once, or `photo_upload`). Costs 5 credits per image from the contractor's own credits, reserved now: 402 `insufficient_credits` (with `required` and `top_up_url`, plus `balance` for an organization key only: a partner key never sees the contractor's balance) when short, and nothing is charged; a partner key also needs the grant's `mockups:write` and stays within the spend cap the contractor set (402 `partner_spend_cap_reached`). Failed images are refunded. Answers 202 with the generation; poll GET /v1/mockup_generations/{id} or subscribe to mockup.completed and mockup.failed. Idempotency-Key is required: a retry with the same key never charges again. At most 3 generations per organization run at once (429 `rate_limited`, `mockup_concurrency`). Finished images join the project as one editor mockup card (named from its light colors in the app, for example Warm White or Candy Cane, unless the contractor renames it). A card generated from a front photo (`photo_url` or `photo_upload`) is not tied to one of the project's front views: the photo is a working copy, removed a few hours after the generation finishes, so the contractor chooses a front view with a photo for that card before generating more from it in the app. Test keys simulate a finished generation with a sample image (no credits).

|   |   |
| --- | --- |
| Scope | `mockups:write` |
| Credential | API key: `Authorization: Bearer lai_live_…` (or `lai_test_…`). Partner keys add `Light-AI-Organization`. |
| Test mode | Works with test keys against simulated test objects. |
| Idempotency | `Idempotency-Key` **required**. See [Idempotency](https://docs.light-ai.app/idempotency). |

## Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `project_id` | string | yes | The project id (`prj_…`). Id prefix `prj_`. |

## Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `Light-AI-Organization` | string |  | Partner keys: the connected organization to act for (`org_…`). Organization keys may omit it. Id prefix `org_`. |
| `Light-AI-Version` | string |  | Pin a dated API version (defaults to the key's version). One of: `2026-11-01`. |
| `Idempotency-Key` | string | yes | Required: one key per logical request. A retry with the same key and body returns the same operation and never charges again (permanently, not only for 24 hours); the same key with a different body is 422 `idempotency_key_reused`. 8–128 characters. |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | string |  | Light AI Gen 3.0 (the only model). Always `light-ai-gen-3.0`. |
| `images` | integer |  | Number of images, 1–4 (default 1). 5 credits each. 1–4. |
| `source` | object |  | Default: the project's front view with its guide lines. Variants by `type`: `front_view`, `photo_url`, `photo_upload`. |
| `source.type` | string |  | Always `front_view`. |
| `source.front_view_id` | string |  | The project's front view (default: the first one with a photo). Its measured guide lines place the lights, as in the app. 1–128 characters. |
| `source.url` | string |  | A public https URL of the front photo (JPEG, PNG or WebP, at most 10 MB; no redirects). At most 2048 characters. |
| `source.upload_id` | string |  | A photo uploaded with POST /v1/projects/{project_id}/photo_uploads. Id prefix `upl_`. |
| `features` | string[] |  | front_view only: the measured features to light (template line-item ids). Default: every feature measured on the view. 1–40 items. |
| `style_preset` | string |  | A starting point for bulb colors and pattern: `classic_warm_white`, `cool_white`, `multicolor`, `candy_cane` (red and white, alternating) or `red_and_green` (alternating). bulb_colors and c9_pattern override it. One of: `classic_warm_white`, `cool_white`, `multicolor`, `candy_cane`, `red_and_green`. |
| `bulb_colors` | object |  | Bulb colors for C9 lines and mini-light areas, as in the app. |
| `bulb_colors.c9` | string[] |  | One of: `Warm White`, `Cool White`, `Red`, `Green`, `Blue`, `Multicolor`, `Purple`, `Orange`, `Pink`, `Yellow`. 1–4 items. |
| `bulb_colors.mini` | string[] |  | One of: `Warm White`, `Cool White`, `Red`, `Green`, `Blue`, `Multicolor`, `Purple`, `Orange`, `Pink`, `Yellow`. 1–4 items. |
| `c9_pattern` | string |  | One of: `all-same`, `alternating`, `double-alternating`, `other`. |
| `c9_pattern_description` | string |  | With c9_pattern `other`: the pattern in words. 1–200 characters. |
| `ambiance` | object |  | Ambiance toggles of the project's template, e.g. {"snow": true, "fullMoon": true} (default: the template's defaults). |
| `instructions` | string |  | Extra preferences in words (at most 500 characters), as in the app's prompt box. At most 500 characters. |

## Response 202

The generation (queued, or the earlier answer for a retried Idempotency-Key).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Id prefix `mgen_`. |
| `object` | string | yes | Always `mockup_generation`. |
| `livemode` | boolean | yes | false for objects created with a test key. |
| `status` | string | yes | `succeeded` when at least one image succeeded (each image has its own status), `failed` when none did. Every failed image is refunded. One of: `queued`, `processing`, `succeeded`, `failed`. |
| `project_id` | string \| null | yes | Id prefix `prj_`. |
| `model` | string | yes | Always `light-ai-gen-3.0`. |
| `model_name` | string | yes |  |
| `images_requested` | integer | yes | 1–4. |
| `images_succeeded` | integer | yes |  |
| `images_failed` | integer | yes |  |
| `source` | object | yes |  |
| `source.type` | string | yes | One of: `front_view`, `photo_url`, `photo_upload`. |
| `source.front_view_id` | string \| null |  |  |
| `options` | object | yes | The options the generation used (preset, bulb colors, pattern, ambiance, features, instructions). |
| `credits` | object | yes |  |
| `credits.per_image` | integer | yes | Credits per image (5, as in the app). |
| `credits.reserved` | integer | yes | Held for images still generating. |
| `credits.charged` | integer | yes | Spent on succeeded images. |
| `credits.refunded` | integer | yes | Returned for failed images. |
| `images` | object[] | yes |  |
| `images[].index` | integer | yes | 1–4. |
| `images[].status` | string | yes | One of: `queued`, `processing`, `succeeded`, `failed`. |
| `images[].mockup_id` | string \| null | yes | The mockup in the project's mockup history (`mk_…`), once succeeded. Id prefix `mk_`. |
| `images[].url` | string \| null | yes | Signed image URL (WebP), valid until expires_at. null in webhook payloads: GET the generation for a fresh URL. |
| `images[].expires_at` | timestamp \| null | yes | RFC 3339 timestamp (UTC, milliseconds). |
| `images[].removed` | boolean | yes | true when the image was generated but has since been deleted from the project. |
| `images[].failure_code` | string \| null | yes | `generation_failed` (the image could not be generated), `generation_paused` (too many recent failures or too much at once for this account: retry in a few minutes), `generation_interrupted`, `generation_unavailable`, `queue_timeout` (the image could not be started within 6 hours: request it again), `source_photo_unavailable`, `project_deleted`. A failed image is always refunded. One of: `generation_failed`, `generation_paused`, `generation_interrupted`, `generation_unavailable`, `queue_timeout`, `source_photo_unavailable`, `project_deleted`. |
| `images[].sample` | boolean |  | Test mode: a fixed sample image, nothing was generated. |
| `failure_code` | string \| null | yes | One of: `generation_failed`, `generation_paused`, `generation_interrupted`, `generation_unavailable`, `queue_timeout`, `source_photo_unavailable`, `project_deleted`. |
| `generated_via` | string | yes | The requester label in the contractor's credit and project history: "Generated via API by <partner>" or "Generated via API key "<name>"". The organization and the partner that made the request see it; any other partner sees "Generated via API". |
| `created_at` | timestamp | yes | RFC 3339 timestamp (UTC, milliseconds). |
| `updated_at` | timestamp | yes | RFC 3339 timestamp (UTC, milliseconds). |
| `completed_at` | timestamp \| null | yes | RFC 3339 timestamp (UTC, milliseconds). |
| `simulated` | boolean |  | Test mode only. |

## Errors

| Status | Meaning |
| --- | --- |
| `400` | Bad request. [`bad_request`](https://docs.light-ai.app/errors#bad_request), [`test_mode_mismatch`](https://docs.light-ai.app/errors#test_mode_mismatch), [`test_mode_unsupported`](https://docs.light-ai.app/errors#test_mode_unsupported), [`unsupported_api_version`](https://docs.light-ai.app/errors#unsupported_api_version) |
| `401` | Missing or invalid credential. [`authentication_required`](https://docs.light-ai.app/errors#authentication_required), [`invalid_api_key`](https://docs.light-ai.app/errors#invalid_api_key), [`api_key_revoked`](https://docs.light-ai.app/errors#api_key_revoked), [`api_key_expired`](https://docs.light-ai.app/errors#api_key_expired), [`api_key_suspended`](https://docs.light-ai.app/errors#api_key_suspended) |
| `402` | Not enough credits (insufficient_credits: `required` and `top_up_url`, plus `balance` for organization keys only; a partner key never sees the contractor's balance), or a partner spend cap the organization set (partner_spend_cap_reached). Nothing was charged. |
| `403` | Not allowed. [`insufficient_scope`](https://docs.light-ai.app/errors#insufficient_scope), [`plan_upgrade_required`](https://docs.light-ai.app/errors#plan_upgrade_required), [`organization_paused`](https://docs.light-ai.app/errors#organization_paused), [`organization_off_season`](https://docs.light-ai.app/errors#organization_off_season), [`api_disabled`](https://docs.light-ai.app/errors#api_disabled), [`api_access_not_enabled`](https://docs.light-ai.app/errors#api_access_not_enabled), [`ip_not_allowed`](https://docs.light-ai.app/errors#ip_not_allowed), [`forbidden`](https://docs.light-ai.app/errors#forbidden) |
| `404` | Not found (also for objects of other organizations and for organizations without an active grant). |
| `409` | Conflict. [`stale_state`](https://docs.light-ai.app/errors#stale_state), [`conflict`](https://docs.light-ai.app/errors#conflict), [`idempotency_in_progress`](https://docs.light-ai.app/errors#idempotency_in_progress), [`idempotency_scope_mismatch`](https://docs.light-ai.app/errors#idempotency_scope_mismatch), [`quote_already_decided`](https://docs.light-ai.app/errors#quote_already_decided), [`quote_not_current`](https://docs.light-ai.app/errors#quote_not_current), [`billing_review_required`](https://docs.light-ai.app/errors#billing_review_required) |
| `413` | Request bodies are limited to 256 KB. |
| `415` | The body is not JSON: send Content-Type: application/json. [`unsupported_media_type`](https://docs.light-ai.app/errors#unsupported_media_type) |
| `422` | Validation failed. [`idempotency_key_reused`](https://docs.light-ai.app/errors#idempotency_key_reused) |
| `429` | Rate limited or daily cap reached. See Retry-After and the RateLimit headers. [`rate_limited`](https://docs.light-ai.app/errors#rate_limited), [`daily_cap_reached`](https://docs.light-ai.app/errors#daily_cap_reached) |
| `500` | Unexpected server error. Safe to retry with exponential backoff; quote the Request-Id to support. [`internal_error`](https://docs.light-ai.app/errors#internal_error) |
| `503` | Temporarily unavailable. Retry with exponential backoff. [`service_unavailable`](https://docs.light-ai.app/errors#service_unavailable) |

## Examples

**Request**

curl:

```bash
curl -X POST "https://api.light-ai.app/v1/projects/{project_id}/mockups" \
  -H "Authorization: Bearer $LIGHT_AI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "images": 2,
  "style_preset": "classic_warm_white",
  "ambiance": {
    "snow": true
  }
}'
```

JavaScript:

```javascript
const response = await fetch('https://api.light-ai.app/v1/projects/{project_id}/mockups', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_AI_API_KEY}`,
    'Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "images": 2,
    "style_preset": "classic_warm_white",
    "ambiance": {
      "snow": true
    }
  }),
});
const data = await response.json();
```

Python:

```python
import os, uuid, requests

response = requests.post(
    "https://api.light-ai.app/v1/projects/{project_id}/mockups",
    headers={
        "Authorization": f"Bearer {os.environ['LIGHT_AI_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
      "images": 2,
      "style_preset": "classic_warm_white",
      "ambiance": {
        "snow": True
      }
    },
    timeout=30,
)
data = response.json()
```

**Response 202**

```json
{
  "id": "mgen_3f9c2a7be41d…",
  "object": "mockup_generation",
  "livemode": false,
  "status": "queued",
  "project_id": "prj_3f9c2a7be41d…",
  "model": "light-ai-gen-3.0",
  "model_name": "string",
  "images_requested": 1,
  "images_succeeded": 1,
  "images_failed": 1,
  "source": {
    "type": "front_view",
    "front_view_id": "string"
  },
  "options": {},
  "credits": {
    "per_image": 0,
    "reserved": 0,
    "charged": 0,
    "refunded": 0
  },
  "images": [
    {
      "index": 1,
      "status": "queued",
      "mockup_id": "mk_3f9c2a7be41d…",
      "url": "https://example.com/…",
      "expires_at": "2026-10-12T14:03:11.000Z",
      "removed": true,
      "failure_code": "generation_failed",
      "sample": true
    }
  ],
  "failure_code": "generation_failed",
  "generated_via": "string",
  "created_at": "2026-10-12T14:03:11.000Z",
  "updated_at": "2026-10-12T14:03:11.000Z",
  "completed_at": "2026-10-12T14:03:11.000Z",
  "simulated": true
}
```
