Skip to content
Lumean API

The asynchronous order model

POST /orders answers 201 and brings no result. Nine statuses, five terminal ones, and one that looks final but isn't.

POST /orders doesn't generate anything. It validates the request, locks the funds and the token quota, queues the task and answers 201 with status created — in this response result is null, and there are no files and cannot be any. The result appears minutes later, and finding out about it is a separate job for the client.

What the 201 response contains

FieldWhat it holds at creation
idThe order UUID. The only thing you must store: every call goes through /orders/{order}/…
statuscreated (the operation description also allows pending — right after the task is queued)
resultnull. Neither files[] nor service_files[] is present in the creation response
total_chunksHow many fragments the server cut the text into. 0 means splitting hasn't happened yet
completed_chunks, progress_percentZeros
eta_seconds, eta_atThe estimate exists while the order is running; for an order that hasn't started and for a finished one it is null (in spec examples it is sometimes already filled in the 201 response)
price_unitsThe locked amount in LMC minor units (the chunk field with the same name holds tokens)

Four order types, not nine

POST /orders requires one of two: template_id or task_type. A template defines the service and all the settings — that's how TTS is created. A bare task_type works only where the service code matches the task type.

task_typeHow it's createdWhere the parameters goFiles in result.files[]
ttsonly via template_idinput_text at the request root1 (the merged track) + subtitles in service_files[]
sfxwithout a templatetask_data.text, plus duration_seconds, prompt_influence, loop, output_formatexactly 1
musicwithout a templatetask_data.prompt, plus n_variants (1..4), music_length_ms, lyrics_text, force_instrumentaln_variants files, not merged
voice_searchwithout a templateaudio in input_files + task_data.top (1..50)no files: the result is a list of similar voices

The description key for SFX is text, for Music it's prompt; they're easy to mix up, and the rejection will say “field is required”. Put the keys at the root of task_data, without the sfx_settings / music_settings wrappers from the configuration reference: in a template the wrapper gives 422, in an order it is silently dropped.

curl -s -X POST "$BASE/orders" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" -d '{
  "task_type": "music",
  "task_data": { "prompt": "Lo-fi hip hop, calm, rainy night", "n_variants": 3,
                 "music_length_ms": 30000, "force_instrumental": true }
}'

The price here depends on the number of generations, not their size: sfx and voice_search are one rate per order, music is the rate x n_variants. A five-minute track costs exactly as much as a ten-second one. The server mixes the service's default_config in on top of your values (disabled with the skip_service_defaults: true flag), so the order's saved task_data will contain keys you never sent.

Gotcha

The TaskType schema in the spec lists eight values, and voice_search isn't among them — even though the type is accepted and orders of that type get created (the enum of the task_type field itself in the POST /orders body has nine values). The flip side: image, txt2img, remix, image_edit and voice_clone pass validation but fail at service resolution with 422 service_not_found — they can't be created with an API key. As a filter, GET /orders?task_type[]=… accepts all nine values; only the four from the table above can be created.

422service_not_foundservice_not_found — no such service
What happened
422 with `reason: "service_not_found"` and without `errors`: the request passed shape validation but failed at service resolution. The order was not created.
What to do right now
Check against the list of creatable types and do not send the rest. The same `reason` appears on `GET /usage/{service}` as a 404: there, `service` is the `service_code` from `GET /usage`, not an arbitrary string.
Open the code page

Nine statuses

Value
created
pending
in_progress
completed
result_delivered
failed
compensated
cancelled
partially_completed

Terminal — five: completed, result_delivered, failed, compensated, cancelled. An order in these statuses no longer changes, polling it further is pointless, and file retention (7 days by default) is counted from the moment of this transition.

Non-terminal — four: created, pending, in_progress, partially_completed.

Gotcha

The description of the Order.status field in the spec lists only four terminal statuses and spells canceled with one l. The fuller list from the GET /orders description is correct: there are five terminal statuses, result_delivered among them. In code, rely on the enum — there and in all response examples the value is spelled cancelled, with two ls; the single-l spelling appears only in prose.

Order lifecycleNine order statuses with the terminal ones marked.runningcreatedpendingin_progresscompletedterminalresult_deliveredterminalpartially_completednot terminalfailedterminalcompensatedterminalcancelledterminal
StatusTerminalWhat to do next
created, pendingnoWait. While the order is here, you can still cancel it
in_progressnoWait, watch completed_chunks / progress_percent
partially_completednoFinished files can already be collected; failed chunks still need to be brought to completion
completedyesCollect result.files[] via POST /storage/url without delay
result_deliveredyesSame: the files are still there until retention expires
failed, compensatedyesresult = null, no error details in the user context. You need a new order
cancelledyesThe order was withdrawn via POST /orders/{order}/cancel; funds and token quota were returned

The spec doesn't explain the difference between failed and compensated; what they share is result: null. Cancellation is accepted only in created, pending and partially_completed, otherwise 400.

The trap: partially_completed is not final

An order moves to partially_completed when some of its chunks failed: the finished fragments are in place, the failed ones await your decision. A handler that treats everything except created/pending/in_progress as terminal will quietly abandon such an order half-done — even though it can be brought to completed with two calls.

202policy_flaggedpolicy_flagged — chunk rejected by policy
What happened
This is not an HTTP error. The code arrives in two ways: as the value of `OrderItem.status`, and as the `reason` in the `skipped[]` array of the 202 response to `POST /orders/{order}/items/retry-failed`. The order itself moves to `partially_completed` — a NON-terminal status: completed chunks stay in place, failed ones wait for action.
What to do right now
Fetch the current text of the chunk, rephrase the offending fragment, and retry the chunk individually with the corrected text. The rejection reason is not exposed in any field — edit at your own discretion. Retry constraints: the new text must be ≤ `original_text_length`, no more than 5 attempts, and single-chunk retry is not available at all for `sfx` and `music`. The order-level `POST /orders/{order}/retry` will not help here — it does not accept `partially_completed`.
Open the code page

The playbook for finishing it is in the Retries and idempotency guide. The order-level POST /orders/{order}/retry won't do for this: it doesn't accept partially_completed and creates a new paid order.

Chunks

Text is split into chunks (OrderItem) before generation, and the work proceeds chunk by chunk. “Chunk” and “fragment” mean the same thing in this documentation: in API fields they are chunk_index, total_chunks, completed_chunks. A chunk has its own statuses that don't match the order statuses: pending, processing, completed, failed (a technical error) and policy_flagged (rejected by the content policy).

The list comes in items[] of GET /orders/{order}; the GET /orders list doesn't have this field. Retries and regenerations live as separate records with the same chunk_index and a parent_item_id reference, so the length of items[] doesn't equal total_chunks. Calculate progress from completed_chunks / total_chunks — only the original chunks count there, otherwise the percentage would climb above a hundred.

Readiness: subscribe or poll

Do

Subscribe to private-order.task.{orderId} (a single order) or private-orders.{userId} (all orders of the account) and listen for status.changed, item.status.changed and progress. The event arrives at the moment of the transition, with no extra requests at all.

Don't

Poll GET /orders/{order} once a second until it's done. That's dozens of useless requests per order, the risk of hitting the key's rate limit (which has no Retry-After), and still up to a second of delay on the most important transition.

Polling isn't forbidden and works perfectly well for short orders — just do it carefully: an interval of 2–5 seconds (the spec doesn't set a rate, but every poll consumes the key's window), base the pause on eta_seconds / eta_at, check a dozen orders with a single GET /orders?status=in_progress instead of N requests, and always set a cap on attempts — failed is an answer too.

Bulk chunk retry is the same case: 202 means “plan accepted”, and the result arrives later via the bulk_retry.completed event or by polling GET /orders/{order}/items.

Tip

The OpenAPI introduction knows two order channels out of three. There are three: private-orders.{userId}, private-order.task.{orderId} and private-chunks.preview.{userId}; the voice clone and gift code channels are not about orders — see the Realtime section.

Files appear later

result.files[] are paths, not links: you exchange them for a temporary URL via POST /storage/url, and it's best to do so right at completed — the files live only until retention. How to tell a live path from a dead one is covered in the Files, links and storage guide.

Feed this page to an AI

Open .mdllm.md

Updated 09/04/2026 · spec 0.0.1