--- title: Image layering | Luma Agents description: Decompose an image into ordered RGBA layers with the Luma Agents API — layer extraction with real transparency on uni-1. --- Decompose an existing image into up to 10 ordered RGBA layers — each a standalone PNG with real transparency, stacked front-to-back so the original composition can be reassembled or re-edited layer by layer. The [uni-1 model](/guides/model/index.md) analyzes the image, plans the split automatically, and extracts each layer as a complete element. For modifying an image in place, see [Image editing](/guides/images/editing/index.md). Layering is in **early access**. It requires the `image_layering` capability on your API client — without the grant, a layering request returns HTTP 400 (`Type 'layering' is not supported for model 'uni-1'`). Contact for access. During early access the layering request and response fields are also absent from the published OpenAPI spec, so the official SDKs do not expose them yet — use the raw HTTP API (the cURL examples below) until general availability. ## When to use layering Use `type: "layering"` when you want to take an image apart — not change it. Each extracted layer is a complete element (the model fills in content hidden behind foreground objects), so layers can be moved, restyled, or recomposed independently. | Scenario | Use | Why | | ------------------------------------------------- | ----------------------- | --------------------------------------------------------------------- | | Separate a subject from its background | `layering` | Both come back as standalone RGBA images | | Build animatable / parallax scenes from a still | `layering` | Ordered depth layers, background last | | Prepare design comps for per-element editing | `layering` | Each element is its own transparent PNG | | Change something in the image (sky, color, style) | `image_edit` + `source` | Edits in place — see [Image editing](/guides/images/editing/index.md) | ## How it works 1. You submit a source image (and optionally a short `prompt` guiding how to split it). 2. The server plans the layers automatically — a vision model analyzes the image before generating, deciding how many layers to extract and what goes in each. 3. Each planned layer is generated as a full RGBA image. Occluded regions are completed, so every layer is a whole element rather than a cutout with holes behind it. 4. The completed generation returns N outputs (up to 10), ordered **front-to-back** — the **last** output is always the opaque background. ## Basic request A layering request requires `type: "layering"` and a `source` image. The `prompt` field is required by the request schema — like every generation request — but for layering it may be an empty string. - [Python](#tab-panel-57) - [TypeScript](#tab-panel-58) - [cURL](#tab-panel-59) - [CLI](#tab-panel-60) ``` from luma_agents import Luma client = Luma() generation = client.generations.create( type="layering", model="uni-1", prompt="separate the subject from the background", source={"url": "https://example.com/product-shot.jpg"}, ) ``` ``` import Luma from "luma-agents"; const client = new Luma(); const generation = await client.generations.create({ type: "layering", model: "uni-1", prompt: "separate the subject from the background", source: { url: "https://example.com/product-shot.jpg" }, }); ``` Terminal window ``` curl -X POST https://agents.lumalabs.ai/v1/generations \ -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "layering", "model": "uni-1", "prompt": "separate the subject from the background", "source": { "url": "https://example.com/product-shot.jpg" } }' ``` Terminal window ``` luma-agents-cli generations create \ --type layering \ --model uni-1 \ --prompt "separate the subject from the background" \ --source.url "https://example.com/product-shot.jpg" ``` ## Source image The `source` field takes the same shape as [image editing](/guides/images/editing#source-image/index.md) — a URL, inline base64 data, an uploaded file, or a prior generation reference. **From a URL:** ``` { "source": { "url": "https://example.com/photo.jpg" } } ``` **From base64 data:** ``` { "source": { "data": "iVBORw0KGgoAAAANSUhEUgAAAAUA...", "media_type": "image/jpeg" } } ``` **From a prior generation** — decompose an image you just generated by passing that completed generation’s top-level `id` as `source.generation_id`. The referenced generation must be owned by the same client: ``` { "source": { "generation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } } ``` **From an uploaded file** — reference a file you uploaded via the [Files API](/guides/files/index.md) by its `file_id`: ``` { "source": { "file_id": "f1e2d3c4-b5a6-7890-abcd-ef0123456789" } } ``` The `source` field is **required** for `type: "layering"`. Provide exactly one of `url`, `data` (with `media_type`), `generation_id`, or `file_id`. ## Guiding the split with `prompt` The `prompt` field must be present on every request, but for layering its content is optional — pass `""` to let the model choose the decomposition on its own. It is also shorter than elsewhere — **at most 500 characters**. It does not describe an image to generate; it guides how the planner splits the source. ``` { "type": "layering", "prompt": "keep the text and logo on their own layers, group the props together", "source": { "url": "https://example.com/poster.jpg" } } ``` Use the prompt to say what should stay together and what should come apart — “one layer per person”, “isolate the bottle, merge everything else into the background”. The planner decides the exact layer count; you steer the grouping. ## Layering options ### `layering.resolution` Sets the output resolution for **every** extracted layer. Defaults to `"1k"`. | Value | Description | | ----- | -------------------------------------------------------------- | | `1k` | Faster and lower cost — the default | | `2k` | Re-renders each layer at higher quality; flat 2× the `1k` rate | - [Python](#tab-panel-61) - [TypeScript](#tab-panel-62) - [cURL](#tab-panel-63) ``` generation = client.generations.create( type="layering", prompt="", source={"url": "https://example.com/product-shot.jpg"}, layering={"resolution": "2k"}, ) ``` ``` const generation = await client.generations.create({ type: "layering", prompt: "", source: { url: "https://example.com/product-shot.jpg" }, layering: { resolution: "2k" }, }); ``` Terminal window ``` curl -X POST https://agents.lumalabs.ai/v1/generations \ -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "layering", "prompt": "", "source": {"url": "https://example.com/product-shot.jpg"}, "layering": {"resolution": "2k"} }' ``` The `layering` block is only valid on `type: "layering"` requests — sending it with any other type returns HTTP 422. ## Validation rules These constraints are specific to `type: "layering"` requests. | Rule | Constraint | | --------------------- | ---------------------------------------------------------------------------------------------------------- | | `type` | Must be `"layering"` | | `model` | Must be `uni-1` — layering is not available on `uni-1-max` | | `source` | **Required.** Provide exactly one of `url`, `data`, `generation_id`, or `file_id` | | `prompt` | Required field, may be empty. **Maximum 500 characters** (guides the split; does not describe a new image) | | `layering.resolution` | `"1k"` or `"2k"`. Defaults to `"1k"` | | `layering.*` | Unknown keys in the `layering` block are **rejected**, not ignored | | `image_ref` | **Rejected** on layering requests | | `web_search` | **Rejected** on layering requests | | `video` | **Rejected** on layering requests | **Common validation errors:** | Mistake | HTTP code | Error | | --------------------------------------- | --------- | ----------------------------------------------------------- | | Missing `source` | 422 | `"source image is required for layering"` | | Including `image_ref` | 422 | `"image_ref is not supported for layering"` | | Including `web_search` | 422 | `"web_search is not supported for layering"` | | Including a `video` block | 422 | `"video options are not supported for layering"` | | `prompt` over 500 characters | 422 | `"prompt must be at most 500 characters for layering"` | | `layering` block on a non-layering type | 422 | `"layering options are only supported for type='layering'"` | See [Error handling](/guides/error-handling/index.md) for the full list of validation errors. ### Asynchronous failures Some problems only surface after submission, when the generation is polled. These fail with `state: "failed"` and a standard [`failure_code`](/guides/error-handling#failure-codes/index.md) — any hold placed at submit is released in full: | Situation | `failure_code` | | ------------------------------------------------------------------------------------ | ------------------- | | The source image passed the submit checks but could not be used for layer extraction | `corrupt_input` | | Layer planning failed for this image — retry, or adjust the prompt guidance | `generation_failed` | | The source image or an output was flagged by content moderation | `content_moderated` | Layer planning failure is the one worth handling explicitly: the vision pass could not produce a usable decomposition for that particular image, and the server already retried once before reporting it. It shows up most on images with no clear foreground/background separation — a flat texture, an abstract pattern, an already-isolated object on plain white. Retrying the identical request rarely helps; adding prompt guidance does. **Layering is all-or-nothing.** The layers form a single front-to-back composition, so a stack missing one layer can’t be recomposed — surviving layers are never returned as a partial result. If any individual layer fails, the whole generation fails and the hold is released **without a charge**. ## Response Layering follows the standard polling flow — submit returns HTTP 201 with `state: "queued"`; poll `GET /v1/generations/{id}` until the state reaches `completed` or `failed`. See the [Quickstart](/index.md) for the polling pattern. On completion, `output` contains one entry per extracted layer, **ordered front-to-back** — `output[0]` is the frontmost element and the **last entry is always the background**. Each output carries a `layer` object describing what the layer contains: ``` { "id": "d290f1ee-6c54-4b01-90e6-d701748f0851", "type": "layering", "state": "completed", "model": "uni-1", "created_at": "2026-07-16T12:00:00Z", "output": [ { "type": "image", "url": "https://storage.example.com/generations/d290f1ee/layer0.png?X-Amz-Expires=3600&...", "layer": { "index": 0, "label": "subject", "description": "A woman in a red coat, complete from head to toe", "alpha_hint": "soft" } }, { "type": "image", "url": "https://storage.example.com/generations/d290f1ee/layer1.png?X-Amz-Expires=3600&...", "layer": { "index": 1, "label": "background", "description": "The full street scene behind the subject", "alpha_hint": "none" } } ], "failure_reason": null, "failure_code": null } ``` ### The `layer` object | Field | Description | | ------------- | ------------------------------------------------------------------------------ | | `index` | Layer position, front-to-back, starting at 0. The last layer is the background | | `label` | Short (1–2 word) layer name | | `description` | Complete-element caption for the layer’s content | | `alpha_hint` | Edge treatment of the layer’s transparency — see below | **`alpha_hint` values:** | Value | Meaning | | ------ | --------------------------------------------------- | | `soft` | Feathered edges — hair, fur, glass, smoke | | `hard` | Clean solid edges — objects, text, graphic elements | | `none` | Fully opaque — the background layer | Every layer is an **RGBA PNG with real transparency** — composite them in order (last first, first last on top) to reassemble the source image. Presigned URLs expire after **1 hour**. Download layers promptly or generate new URLs by polling the endpoint again. ## Pricing Layering is priced **per generation, by `layering.resolution`**. The rate is flat — it does not depend on how many layers come back. | `layering.resolution` | Price per generation | | --------------------- | -------------------: | | `1k` | $0.2250 | | `2k` | $0.4500 | A 2-layer result and a 10-layer result cost the same at the same resolution, so you know the charge before you submit. The hold placed at submit equals the final charge — nothing is released on a successful generation. A failed generation releases the hold in full and is never charged. These are **introductory rates** for early access and are subject to change ahead of general availability. Layering is not yet listed on the [Pricing](/guides/pricing/index.md) page — confirm current rates with before committing to high-volume workloads. ## Rate limits Layering counts against your standard generation limits — see [Rate limits and headers](/guides/rate-limits/index.md). Rejections return HTTP 429 with `Retry-After` and the standard `X-RateLimit-*` headers, before any hold is placed, so a 429 never charges you. A layering request occupies far more capacity than its single request slot suggests: one submission fans out to as many as ten image generations. Pace accordingly — treat one layering submission as up to ten generations, and prefer a modest concurrency ceiling with retries over bursting. If your integration needs sustained layering throughput, contact so headroom can be sized for your account. ## Next steps - [**Image generation**](/guides/images/generation/index.md) — Generate new images from text - [**Image editing**](/guides/images/editing/index.md) — Modify existing images with text prompts - [**Files API**](/guides/files/index.md) — Upload a source once, reference it by `file_id` - [**Models**](/guides/model/index.md) — Model capabilities and limitations - [**Pricing**](/guides/pricing/index.md) — Pay-as-you-go and Provisioned Throughput plans - [**Error handling**](/guides/error-handling/index.md) — Every error code with troubleshooting steps - [**API Reference**](/api/index.md) — Complete endpoint specifications