Image layering
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 analyzes the image, plans the split automatically, and extracts each layer as a complete element. For modifying an image in place, see Image editing.
When to use layering
Section titled “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 |
How it works
Section titled “How it works”- You submit a source image (and optionally a short
promptguiding how to split it). - 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.
- 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.
- The completed generation returns N outputs (up to 10), ordered front-to-back — the last output is always the opaque background.
Basic request
Section titled “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.
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" },});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" } }'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
Section titled “Source image”The source field takes the same shape as image editing — 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 by its file_id:
{ "source": { "file_id": "f1e2d3c4-b5a6-7890-abcd-ef0123456789" }}Guiding the split with prompt
Section titled “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" }}Layering options
Section titled “Layering options”layering.resolution
Section titled “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 |
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" },});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"} }'Validation rules
Section titled “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 for the full list of validation errors.
Asynchronous failures
Section titled “Asynchronous failures”Some problems only surface after submission, when the generation is polled. These fail with state: "failed" and a standard failure_code — 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.
Response
Section titled “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 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
Section titled “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.
Pricing
Section titled “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.
Rate limits
Section titled “Rate limits”Layering counts against your standard generation limits — see Rate limits and headers. 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 support+luma-agents-api@lumalabs.ai so headroom can be sized for your account.
Next steps
Section titled “Next steps”- Image generation — Generate new images from text
- Image editing — Modify existing images with text prompts
- Files API — Upload a source once, reference it by
file_id - Models — Model capabilities and limitations
- Pricing — Pay-as-you-go and Provisioned Throughput plans
- Error handling — Every error code with troubleshooting steps
- API Reference — Complete endpoint specifications