Quickstart
Generate and edit images and videos with the Luma Agents REST API — quickstart, SDKs, and authentication.
The Luma Agents API provides access to Luma’s image and video models — uni-1 and uni-1-max for images, Ray 3.2 for video — through a single async REST surface. Submit a generation request, poll for the result, and download your output.
Quickstart
Section titled “Quickstart”The workflow is three steps:
- Submit a generation request via
POST /v1/generations - Poll for status via
GET /v1/generations/{generation_id} - Download images or videos from presigned URLs once the state reaches
completed
Generate an image
Section titled “Generate an image”import timefrom luma_agents import Luma
client = Luma()
# 1. Submitgeneration = client.generations.create( prompt="A glass of iced coffee on a marble countertop, morning light streaming through a window", aspect_ratio="16:9",)
# 2. Pollwhile generation.state not in ("completed", "failed"): time.sleep(2) generation = client.generations.get(generation.id)
# 3. Downloadif generation.state == "completed": print(f"Image URL: {generation.output[0].url}")else: print(f"Failed: {generation.failure_reason} (code: {generation.failure_code})")import Luma from "luma-agents";
const client = new Luma();
// 1. Submitlet generation = await client.generations.create({ prompt: "A glass of iced coffee on a marble countertop, morning light streaming through a window", aspect_ratio: "16:9",});
// 2. Pollwhile (generation.state !== "completed" && generation.state !== "failed") { await new Promise((r) => setTimeout(r, 2000)); generation = await client.generations.get(generation.id);}
// 3. Downloadif (generation.state === "completed") { console.log(`Image URL: ${generation.output![0].url}`);} else { console.error(`Failed: ${generation.failure_reason} (code: ${generation.failure_code})`);}package main
import ( "context" "fmt" "log" "time"
"github.com/lumalabs/luma-agents-go")
func main() { if err := run(context.Background()); err != nil { log.Fatal(err) }}
func run(ctx context.Context) error { client := lumaagents.NewClient()
// 1. Submit generation, err := client.Generations.New(ctx, lumaagents.GenerationNewParams{ Prompt: lumaagents.F("A glass of iced coffee on a marble countertop, morning light streaming through a window"), AspectRatio: lumaagents.F(lumaagents.GenerationNewParamsAspectRatio16_9), }) if err != nil { return fmt.Errorf("submit: %w", err) }
// 2. Poll for generation.State != lumaagents.GenerationStateCompleted && generation.State != lumaagents.GenerationStateFailed { time.Sleep(2 * time.Second) generation, err = client.Generations.Get(ctx, generation.ID) if err != nil { return fmt.Errorf("poll: %w", err) } }
// 3. Download if generation.State == lumaagents.GenerationStateCompleted { fmt.Printf("Image URL: %s\n", generation.Output[0].URL) } else { fmt.Printf("Failed: %s (code: %s)\n", generation.FailureReason, generation.FailureCode) } return nil}# 1. SubmitRESPONSE=$(curl -s -X POST https://agents.lumalabs.ai/v1/generations \ -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "A glass of iced coffee on a marble countertop, morning light streaming through a window", "aspect_ratio": "16:9" }')
ID=$(echo "$RESPONSE" | jq -r '.id')
# 2. Pollwhile true; do RESULT=$(curl -s -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ "https://agents.lumalabs.ai/v1/generations/$ID") STATE=$(echo "$RESULT" | jq -r '.state') [ "$STATE" = "completed" ] || [ "$STATE" = "failed" ] && break sleep 2done
# 3. Downloadecho "$RESULT" | jq -r '.output[0].url'Generate a video
Section titled “Generate a video”Set model to "ray-3.2", type to "video", and pass output options under video. Polling and download work the same way.
generation = client.generations.create( model="ray-3.2", type="video", prompt="A slow dolly shot through a misty greenhouse at sunrise", aspect_ratio="16:9", video={ "resolution": "720p", "duration": "5s", },)const generation = await client.generations.create({ model: "ray-3.2", type: "video", prompt: "A slow dolly shot through a misty greenhouse at sunrise", aspect_ratio: "16:9", video: { resolution: "720p", duration: "5s", },});generation, err := client.Generations.New(ctx, lumaagents.GenerationNewParams{ Model: lumaagents.F(lumaagents.ModelRay3_2), Type: lumaagents.F(lumaagents.GenerationNewParamsTypeVideo), Prompt: lumaagents.F("A slow dolly shot through a misty greenhouse at sunrise"), AspectRatio: lumaagents.F(lumaagents.GenerationNewParamsAspectRatio16_9), Video: lumaagents.F(lumaagents.VideoOptionsParam{ Resolution: lumaagents.F(lumaagents.VideoResolution720p), Duration: lumaagents.F(lumaagents.VideoDuration5s), }),})curl -X POST https://agents.lumalabs.ai/v1/generations \ -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "ray-3.2", "type": "video", "prompt": "A slow dolly shot through a misty greenhouse at sunrise", "aspect_ratio": "16:9", "video": { "resolution": "720p", "duration": "5s" } }'For image-to-video, pass an ImageRef as video.start_frame and/or video.end_frame. See Video generation for the full parameter set.
Edit an image
Section titled “Edit an image”To edit an existing image instead, set type to "image_edit" and provide a source. Polling and download are identical to the generate flow above.
generation = client.generations.create( type="image_edit", prompt="Change the sky to a dramatic sunset with orange and purple clouds", source={"url": "https://example.com/landscape.jpg"},)const generation = await client.generations.create({ type: "image_edit", prompt: "Change the sky to a dramatic sunset with orange and purple clouds", source: { url: "https://example.com/landscape.jpg" },});generation, err := client.Generations.New(ctx, lumaagents.GenerationNewParams{ Type: lumaagents.F(lumaagents.GenerationNewParamsTypeImageEdit), Prompt: lumaagents.F("Change the sky to a dramatic sunset with orange and purple clouds"), Source: lumaagents.F(lumaagents.ImageRefParam{ URL: lumaagents.F("https://example.com/landscape.jpg"), }),})curl -X POST https://agents.lumalabs.ai/v1/generations \ -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "image_edit", "prompt": "Change the sky to a dramatic sunset with orange and purple clouds", "source": { "url": "https://example.com/landscape.jpg" } }'See Image editing for image_ref, style transfer, and base64 source images. To edit a video, set type to "video_edit" with a source video — source.generation_id, a hosted source.url, or inline source.data — see Video editing. To change a video’s aspect ratio, use type: "video_reframe" — see Video reframing.
Production polling
Section titled “Production polling”Don’t bother with exponential backoff — uni-1 generations typically take 30–60 seconds, so polling every 2 seconds is only ~15–30 GETs per job. What’s worth adding: a hard deadline so a stalled job can’t hang your worker, and a short initial wait so the first few polls aren’t wasted.
For video, generations take longer — tune the initial wait and deadline upward (30 seconds initial, 10-minute hard timeout is a reasonable starting point). See Video generation — Polling for a video-shaped template.
deadline = time.time() + 120 # 2-minute hard timeout
time.sleep(20) # uni-1 p50 is ~30s — no point polling sooner
while generation.state not in ("completed", "failed"): if time.time() > deadline: raise TimeoutError(f"Generation {generation.id} did not complete in time") generation = client.generations.get(generation.id) time.sleep(2)const deadline = Date.now() + 120_000; // 2-minute hard timeout
await new Promise((r) => setTimeout(r, 20_000)); // uni-1 p50 is ~30s
while (generation.state !== "completed" && generation.state !== "failed") { if (Date.now() > deadline) { throw new Error(`Generation ${generation.id} did not complete in time`); } generation = await client.generations.get(generation.id); await new Promise((r) => setTimeout(r, 2_000));}deadline := time.Now().Add(2 * time.Minute)
time.Sleep(20 * time.Second) // uni-1 p50 is ~30s
for generation.State != lumaagents.GenerationStateCompleted && generation.State != lumaagents.GenerationStateFailed { if time.Now().After(deadline) { return fmt.Errorf("generation %s did not complete in time", generation.ID) } generation, err = client.Generations.Get(ctx, generation.ID) if err != nil { return fmt.Errorf("poll: %w", err) } time.Sleep(2 * time.Second)}DEADLINE=$(($(date +%s) + 120)) # 2-minute hard timeoutsleep 20 # uni-1 p50 is ~30s
while true; do if [ "$(date +%s)" -gt "$DEADLINE" ]; then echo "Generation $ID did not complete in time" >&2 exit 1 fi RESULT=$(curl -s -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ "https://agents.lumalabs.ai/v1/generations/$ID") STATE=$(echo "$RESULT" | jq -r '.state') [ "$STATE" = "completed" ] || [ "$STATE" = "failed" ] && break sleep 2doneAuthentication
Section titled “Authentication”Every request requires a Bearer token in the Authorization header. Set your API key as the LUMA_AGENTS_API_KEY environment variable — the official SDKs read it automatically.
export LUMA_AGENTS_API_KEY="luma-api-..."Base URL
Section titled “Base URL”https://agents.lumalabs.ai/v1Endpoints
Section titled “Endpoints”| Method | Path | Description |
|---|---|---|
POST | /v1/generations | Submit an image or video generation, edit, or reframe job |
GET | /v1/generations/{generation_id} | Poll for generation status and output |
Attributing requests to end users
Section titled “Attributing requests to end users”If you build on the Agents API on behalf of your own users, pass an optional user_id on POST /v1/generations — a stable, opaque identifier for the end user (no PII, max 256 characters):
{ "prompt": "A sunset over the ocean", "user_id": "your-internal-user-id"}It’s forwarded to upstream model providers as their per-user tagging field, so trust & safety issues can be attributed to a specific end user rather than your whole account, and it powers per-end-user usage breakdowns. Strongly recommended for partner integrations.
pip install luma-agentsnpm install luma-agentsgo get github.com/lumalabs/luma-agents-gogo install github.com/lumalabs/luma-agents-cli/cmd/luma-agents-cli@latestResponse headers
Section titled “Response headers”Every response includes X-Request-Id and X-API-Version headers. Successful generation requests also include rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset). See Rate limits and headers for details.
Next steps
Section titled “Next steps”- Models — Capabilities for
uni-1,uni-1-max,ray-3.2 - Image generation — Full parameter reference for text-to-image
- Image editing — Modify existing images with text prompts and reference images
- Video generation — Ray 3.2 text-to-video and image-to-video
- Video editing — Ray 3.2 video editing
- Video reframing — Ray 3.2 aspect-ratio reframing
- Pricing — Pay-as-you-go and Provisioned Throughput plans
- Rate limits and headers — Rate limiting, response headers, and retry strategies
- Error handling — Every error code with troubleshooting steps
- FAQ — Quick answers to common questions
- API Reference — Complete endpoint specifications