Skip to content
lumalabs.ai

Files

Upload files once with the Luma Agents Files API and reference them across many image and video generations by file_id.

The Files API lets you upload an image or video once and reference it from many generations by its file_id — instead of re-sending a URL or re-encoding base64 on every request. Uploaded files are moderated once at ingest, so referencing one later skips the per-request fetch/decode and the moderation round-trip.

A file_id is one of the media reference shapes accepted anywhere ImageRef is — source, image_ref[], video.start_frame / end_frame, and video.keyframes. Use whichever fits:

You haveUse
Media you’ll reference more than oncefile_id (upload it)
A one-off, publicly reachable URLurl
Small bytes you already hold in memorydata (base64)
The output of a prior generationgeneration_id

Uploading is worth it when the same asset feeds several generations, when the bytes aren’t publicly hosted, or when you want moderation to happen up front rather than on the generation’s critical path.

There are two ways to upload, sharing one endpoint (POST /v1/files). Pick by Content-Type.

Send the bytes directly as multipart/form-data. Simplest for small assets; subject to an inline size cap (larger files must use the presigned flow below).

Terminal window
curl -X POST https://agents.lumalabs.ai/v1/files \
-H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \
-F "file=@./reference.jpg" \
-F "purpose=reference"

The response is a CreateFileResponse. For an inline upload, upload is null and the file is already pending ingest — no follow-up call needed.

{
"id": "f1e2d3c4-b5a6-7890-abcd-ef0123456789",
"state": "pending",
"upload": null,
"file": {
"id": "f1e2d3c4-b5a6-7890-abcd-ef0123456789",
"filename": "reference.jpg",
"mime_type": "image/jpeg",
"size_bytes": 248173,
"purpose": "reference",
"state": "pending",
"created_at": "2026-06-11T12:00:00Z"
}
}

For larger files, request a presigned upload, PUT the bytes straight to storage, then finalize. Three steps:

1. Request the upload. Declare mime_type and exact size_bytes:

Terminal window
curl -X POST https://agents.lumalabs.ai/v1/files \
-H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "scene.mp4",
"mime_type": "video/mp4",
"size_bytes": 734003200,
"purpose": "input"
}'

The response carries an upload envelope:

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"state": "pending",
"upload": {
"url": "https://storage.example.com/...&X-Amz-Signature=...",
"method": "PUT",
"headers": { "Content-Type": "video/mp4" },
"expires_at": "2026-06-11T13:00:00Z"
},
"file": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"state": "pending",
"...": "..."
}
}

2. Upload the bytes with an HTTP PUT to upload.url, sending exactly the upload.headers and the size_bytes you declared:

Terminal window
curl -X PUT "<upload.url>" \
-H "Content-Type: video/mp4" \
--data-binary @./scene.mp4

3. Complete the upload to kick off ingest:

Terminal window
curl -X POST https://agents.lumalabs.ai/v1/files/a1b2c3d4-e5f6-7890-abcd-ef0123456789/complete \
-H "Authorization: Bearer $LUMA_AGENTS_API_KEY"

After upload, ingest and moderation run asynchronously. Poll GET /v1/files/{file_id} until the file reaches a terminal state:

StateMeaning
pendingBytes received; ingest/moderation in progress. Not yet referenceable.
readyAvailable — reference it from a generation by file_id.
failedIngest or moderation rejected the file; see failure_reason.
deletedSoft-deleted; can no longer be referenced.

A file must be ready before a generation can reference it. Referencing a pending, failed, or deleted file is rejected.

Once a file is ready, pass its id as file_id anywhere an ImageRef is accepted. The shape is interchangeable with url / data / generation_id — provide exactly one.

As the edit source:

generation = client.generations.create(
type="image_edit",
prompt="Change the sky to a dramatic sunset",
source={"file_id": "f1e2d3c4-b5a6-7890-abcd-ef0123456789"},
)

As a reference image (and the same form works for video.start_frame, video.end_frame, and video.keyframes[]):

{
"type": "image",
"prompt": "A product photo in the same lighting as the reference",
"image_ref": [{ "file_id": "f1e2d3c4-b5a6-7890-abcd-ef0123456789" }]
}

List your files, newest first (keyset-paginated — pass next_cursor back as cursor):

Terminal window
curl "https://agents.lumalabs.ai/v1/files?limit=25&purpose=reference" \
-H "Authorization: Bearer $LUMA_AGENTS_API_KEY"

Get a single file’s metadata:

Terminal window
curl https://agents.lumalabs.ai/v1/files/f1e2d3c4-b5a6-7890-abcd-ef0123456789 \
-H "Authorization: Bearer $LUMA_AGENTS_API_KEY"

Delete (soft-delete — the file can no longer be referenced; returns 204):

Terminal window
curl -X DELETE https://agents.lumalabs.ai/v1/files/f1e2d3c4-b5a6-7890-abcd-ef0123456789 \
-H "Authorization: Bearer $LUMA_AGENTS_API_KEY"
FieldNotes
purposeinput (a primary subject, e.g. an edit source) or reference (style/content guidance). Defaults to input.
mime_typeRequired in the presigned flow; sniffed from the bytes in the inline flow.
size_bytesRequired (and enforced) in the presigned flow. Up to 5 GiB (the storage single-PUT ceiling).
expires_atOptional TTL; the file becomes eligible for deletion afterward.
user_idOptional opaque end-user tag for abuse attribution — mirrors user_id on generations. Echoed back unchanged.
HTTPWhen
401Missing or invalid API key
404The file isn’t yours, or doesn’t exist
413Inline upload over the size cap, or your storage / file-count quota is full
415Content-Type is neither multipart/form-data nor application/json
422Invalid body or form fields; on complete, the bytes haven’t been PUT yet
429Rate limit exceeded
503File ingest pipeline temporarily unavailable — retry shortly