Files, links and storage
Why result.files[] can't be downloaded, where the subtitles are, and how to tell a live path from a dead one.
Translation in progress
A finished order returns its result in result.files[]. These are paths inside storage, not URLs:
a GET on such a string returns nothing. Downloading always takes two steps: exchange the path for a
temporary link, then download from the link.
Exchanging a path for a link
POST /storage/url takes the full path, exactly as it appears in the order, and returns data.url —
a signed temporary link. It's the only public API operation that requires
the orders.download scope: with a key that has orders.read +
orders.write, the order is visible but the file won't download — 403 with no reason at all.
| Field | Rule | Default | Purpose |
|---|---|---|---|
path | required, string | — | A string from result.files[] or result.service_files[] |
download | boolean | false | true — the link is marked as an attachment: the file is saved under the order name (Order.name) instead of opening in a tab |
number_base | integer 0 or 1 | 1 | The number that chunk numbering in the file name starts from |
number_padding | integer 1..4 | 3 | Chunk number width, padded with leading zeros (3 → “007”) |
The link lives for a limited time (per the spec, about an hour) and is downloaded with a plain GET without the
X-API-KEY header: the signature is already inside the URL. Don't store it in a database or hand it out
for long — request it right before downloading. If it has expired, repeat POST /storage/url for the same
path; the file itself isn't affected.
Get File URL
Two file containers, not one
Subtitles and alignment are not in files[]. An order has two independent flat arrays
of path strings:
| Field | What's inside |
|---|---|
result.files[] | The main result. For TTS after merging — exactly one element (the final track); for music — one file per each of the n_variants |
result.service_files[] | .srt / .vtt / .lrc subtitles, alignment, result.json with word-level timings |
Gotcha
service_files are generated only by TTS services, and the field is present only when the set is non-empty:
for sfx and music it doesn't exist at all — not null and not [], but a missing key, so you have to check
whether the key is present. And it exists only at the order level:
GET /orders/{order}/items/{item} returns only the chunk's own result_file. Anyone looking for
subtitles in files[] or in a chunk will never find them.
An element has no separate type field — the type is told apart by the extension and by the
/service/ segment in the string itself. raw_chunk and metadata paths aren't visible to an API key consumer;
an attempt to exchange such a path gives 403.
Recipe: download everything there is
The whole pipeline:
# 1. The order: the paths are in result, and the status must be terminal
curl -s "$BASE/orders/$ORDER_ID" -H "X-API-KEY: $KEY"
# 2. Every path from files[] AND from service_files[] is exchanged for a link
curl -s -X POST "$BASE/storage/url" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
-d '{ "path": "storage/501/orders/<ORDER_UUID>/output/final/result.mp3", "download": true }'
# → { "success": true, "data": { "url": "https://s3.lumean.app/…?X-Amz-Signature=…" } }
# 3. Download from data.url with a plain GET, without X-API-KEY
curl -sL -o result.mp3 "$URL"You can also collect files from a partially_completed order: the successful
chunks are already finished. A failed order (failed, compensated) has result equal to null —
there's nothing to download, and there are no error details on the public surface either.
Retention: two levels and two deadlines
Order files are deleted by retention — by default 7 days after the transition to a
terminal status (completed, result_delivered, failed, compensated, cancelled).
There are two storage levels, and the per-chunk tracks disappear earlier than the finished result:
| Field | Meaning |
|---|---|
files_expire_at / retention.files.expires_at | When all of the order's files will disappear. null — the order isn't terminal yet or the files have already been deleted |
files_expired / retention.files.expired | true — the files are gone, and a link can't be obtained |
retention.chunks.expires_at / .expired | The deadline for the per-chunk tracks: while they're alive, pause editing, reassembly and chunk regeneration are available |
retention.chunks.expires_early | true — the chunks will disappear before the result (the order has been merged into a separate file) |
retention.chunks.reason | chunks_purged (no chunks, the result is in place), storage_expired (nothing left), null (everything is available) |
Careful
The main trap: after retention expires, GET /orders/{order} still shows the old
paths in result.files[]. The presence of a path proves nothing — check files_expired
(or retention.files.expired), and ultimately the result of the link exchange. A dead path
differs from a live one only by the 404 code from POST /storage/url.
- What happened
- `POST /storage/url` for a path from `result.files[]` returns 404 `{"success": false, "message": "File not found."}`. Under the same name, the code also appears as a field in the chunk preview response — next to the `chunks_purged` value, which means only the fragments were deleted and the result is still there.
- What to do right now
- The main trap: after retention expires, `GET /orders/{order}` STILL shows the old paths in `result.files[]`. The user-facing response has no separate expiry flag — a "dead" path can be told apart from a live one only by the 404 when you request a link. The files cannot be restored: they no longer exist. The right habit is to download the result as soon as the order is `completed`, without delay; subtitles are stored separately, in `result.service_files[]`, and are deleted along with everything else.
Deleted files can't be restored: the order record stays in the database, but the files themselves no longer
exist. The only way to get the result again is
POST /orders/{order}/retry, which creates a
new order with the same parameters and is billed again.
Do
Download the result right at completed — both files[] and service_files[] — and before
downloading an old order, check files_expired.
Don't
Store paths or issued links as permanent addresses: a link goes stale within an hour, a path within a week, and both remain in the response after they're dead.
Feed this page to an AI
Updated 04/09/2026 · spec 0.0.1