---
title: "Files, links and storage"
url: https://lumean.app/es/developers/docs/guide/files
locale: es
description: "Why result.files[] can't be downloaded, where the subtitles are, and how to tell a live path from a dead one."
updated: 2026-09-04
source_locale: en
sources: [get-orders-order, post-storage-url, post-orders-order-retry]
errors: ["404", storage-expired, "403"]
spec_version: 0.0.1
---

# Files, links and storage

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](/developers/docs/guide/authentication): 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.

### POST /storage/url

Get File URL

Справочник: https://lumean.app/es/developers/docs/api/post-storage-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 |

> [!WARNING]
> `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:

```text
Конвейер публичного API: от ключа до ссылки на готовый файл

  1. Ключ        X-API-KEY            заголовок в каждом запросе
        ↓
  2. Права       orders.write         пресет ключа решает, что он может
        ↓
  3. Голос       GET /voices          берём voice_id из каталога
        ↓
  4. Шаблон      POST /templates      voice_id кладётся в config.tts_settings
        ↓                             (поля orders.voice_id НЕ существует)
  5. Заказ       POST /orders          201 created, БЕЗ результата
        ↓
  6. Чанки       GET /orders/{order}   статус и items, пока не терминальный
        ↓
  7. Файлы       result.files[]        это ПУТИ в хранилище, а не URL
        ↓
  8. Ссылка      POST /storage/url     путь → временная ссылка на скачивание
```

```bash
# 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`](/developers/docs/guide/async) 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) |

> [!CAUTION]
> 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`.

### 404 · `storage_expired` — storage_expired — files deleted by retention

| Что произошло | Что делать |
| --- | --- |
| `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. | 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. |

`Retry-After`: нет. Разбор: https://lumean.app/es/developers/docs/errors/storage-expired

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`](/developers/docs/api/post-orders-order-retry), which creates a
**new** order with the same parameters and is billed again.

**Правильно**

Download the result right at `completed` — both `files[]` and `service_files[]` — and before
downloading an old order, check `files_expired`.

**Неправильно**

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.

---

- Esta página en el navegador: https://lumean.app/es/developers/docs/guide/files
- Referencia completa legible por máquina: https://api.lumean.app/docs/llm.md
