Skip to content
Console

Library

Assets

Copy page

The library of everything your organisation has generated or uploaded — list and search it, upload files, organise them into folders, delete them.

Every file the API makes for you — an image, a clip, a skybox, stored speech, a transcript — lands in your organisation’s asset library, and so does every file you upload. Each one is an asset with an ast_… id: the id you pass as a reference image, a video frame, or audio to transcribe.

curl
curl "https://api.lumavox.ai/v1/assets?media_type=image&limit=20" \
-H "Authorization: Bearer $API_KEY"
Response
{
"data": [
{
"id": "ast_a1B2c3D4e5F6g7H8",
"name": "A weathered brass diving helmet",
"kind": "output",
"media_type": "image",
"content_type": "image/jpeg",
"size_bytes": 412377,
"duration_seconds": null,
"width": 1328,
"height": 752,
"peaks": null,
"folder_id": null,
"capability": "image",
"origin": "api",
"thumbnail_url": "https://…",
"status": "ready",
"error": null,
"created_at": "2026-10-24T09:14:21.904Z"
}
],
"next_cursor": "eyJrIjoiYXNzZXRzOmNyZWF0ZWRfYXQi…"
}
Scope Allows
assets Listing and reading assets and folders, and uploading files.
assets:write Renaming, moving and deleting assets, and creating, changing and deleting folders.

They are separate on purpose: a key that reads your generations back cannot empty the library. See Authentication.

GET /v1/assets returns the library newest first, a page at a time.

Query What it does
limit Page size, 1–200. Default 50.
cursor The next_cursor from the previous page.
kind input (you uploaded it) or output (a service made it).
media_type image, video, audio or data.
capability The capability that produced it, such as image, video or tts. Uploads have none.
request_id req_… — everything one API call produced.
search Matches anywhere in the name, case-insensitively, up to 120 characters. Taken literally — % and _ are ordinary characters.
sort created_at (default), name, size_bytes or duration_seconds.
order desc (default) or asc.

The answer is a page: keep passing next_cursor back as cursor, with the same sort, until it comes back null (Conventions). A cursor belongs to the sort it was made with: one from another sort, or one that cannot be read, is a 422 — never the first page again.

Each item carries a thumbnail_url — a short-lived link to a 512-pixel square preview — for images, video and skyboxes. Audio has none; it has peaks instead, a waveform of magnitudes from 0 to 100 for drawing (an empty list for silence, null when never measured). duration_seconds is how long audio or video runs, null for a still.

GET /v1/assets/{asset_id} returns everything in the listing plus links to the file itself.

curl
curl https://api.lumavox.ai/v1/assets/ast_a1B2c3D4e5F6g7H8 \
-H "Authorization: Bearer $API_KEY"
Response
{
"id": "ast_a1B2c3D4e5F6g7H8",
"name": "A weathered brass diving helmet",
"kind": "output",
"media_type": "image",
"content_type": "image/jpeg",
"size_bytes": 412377,
"duration_seconds": null,
"width": 1328,
"height": 752,
"peaks": null,
"folder_id": null,
"capability": "image",
"origin": "api",
"thumbnail_url": "https://…",
"status": "ready",
"error": null,
"created_at": "2026-10-24T09:14:21.904Z",
"request_id": "req_k3J9mQ2vX7pL4nRt",
"end_user_id": null,
"meta": null,
"url": "https://…",
"url_expires_at": "2026-10-24T09:45:12.000Z",
"preview_url": null,
"variants": [
{ "name": "50", "width": 664, "height": 376, "size_bytes": 118204, "url": "https://…" },
{ "name": "25", "width": 332, "height": 188, "size_bytes": 34110, "url": "https://…" },
{ "name": "12.5", "width": 166, "height": 94, "size_bytes": 9873, "url": "https://…" }
]
}
Field What it is
url A time-limited link to the file; null when file storage is unavailable.
url_expires_at When url, preview_url and the variants’ links stop working — fifteen minutes after the response. null when there is no url.
preview_url A displayable copy when url is not one. Set for skyboxes, whose url is a Radiance .hdr; null otherwise.
variants Smaller copies of an image — 50, 25 and 12.5 per cent of the original, largest first — each with its own url. Empty for anything that has none.
request_id The req_… of the call that produced it.
end_user_id The end user it was made for, if any.
meta Anything else recorded about it.

Links are made fresh on every response and never stored. Read the asset again for a new one whenever an old link has expired; the asset itself stays until you delete it.

An id that does not exist and an id that belongs to another organisation are the same 404.

Uploads go in three steps, and the bytes never pass through the API: you ask for an upload URL, PUT the file straight to storage, then confirm.

1. Ask where to upload — name the type you are about to send:

curl
curl https://api.lumavox.ai/v1/assets/upload-url \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content_type": "image/png" }'
Response
{
"asset_id": "ast_a1B2c3D4e5F6g7H8",
"url": "https://…",
"expires_at": "2026-10-24T09:45:00.000Z"
}

Nothing is created yet. The URL accepts the upload until expires_at, fifteen minutes after you asked; ask for another if it runs out.

2. Upload the bytes to that URL with a PUT, unmodified, with exactly the Content-Type you named — it is signed into the URL — and no Authorization header:

curl
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/png" \
--data-binary @photo.png

3. Confirm once the PUT has succeeded:

curl
curl https://api.lumavox.ai/v1/assets/uploaded \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"asset_id": "ast_a1B2c3D4e5F6g7H8",
"content_type": "image/png",
"filename": "photo.png",
"folder_id": "fld_a1B2c3D4e5F6g7H8"
}'

The answer is a 202 with the asset, status: "pending". The stored file’s type and size are checked, then it is converted in the background. Poll GET /v1/assets/{asset_id} until status is ready — or failed, with the reason in error. A pending asset cannot yet be used as a reference or a frame.

name and folder_id are optional on the confirm. Without a name, the asset is named after filename without its extension.

Kind Types Largest
Images image/jpeg, image/png, image/webp, image/gif, image/heic, image/heif, image/tiff, image/bmp, image/avif limits.uploads.image_bytes
Audio audio/mpeg, audio/mp3, audio/mp4, audio/x-m4a, audio/aac, audio/wav, audio/x-wav, audio/wave, audio/vnd.wave, audio/webm, audio/ogg, audio/opus, audio/flac, audio/x-flac limits.uploads.audio_bytes

The largest file of each kind is the platform’s, listed on Limits with what it is now.

Audio uploads are what transcription reads. An upload that is empty, over the limit, or stored as a different type from the one confirmed is deleted and refused with a 422.

Your organisation can hold a limited number of uploaded files (limits.org.asset_uploads on Limits). At the limit, asking for an upload URL — or confirming one — is a 409 that names the number; delete some uploads to make room.

Status When
404 (Confirm) folder_id is not one of your folders.
409 The PUT has not landed yet, the upload was already confirmed, or your organisation is at its upload limit.
422 A type that is not accepted, or a stored file that is empty, too large, or not the type confirmed. issues names content_type or asset_id.
503 File storage is unavailable.

PATCH /v1/assets/{asset_id} needs assets:write. Send name (1–200 characters), folder_id, or both:

curl
curl -X PATCH https://api.lumavox.ai/v1/assets/ast_a1B2c3D4e5F6g7H8 \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Helmet, final", "folder_id": null }'

folder_id has three states: leave it out to keep the asset where it is, send null to move it to the root, or send an fld_… id to move it there.

To move many at once, POST /v1/assets/move with up to 500 ids and a folder_id (or null for the root). It answers {"affected": n} — ids that are not yours are skipped and simply not counted.

DELETE /v1/assets/{asset_id} needs assets:write. The asset leaves the library at once, and its stored bytes — every copy and the thumbnail — are removed shortly after. It answers 204, with no body.

To delete many, POST /v1/assets/delete with up to 500 ids:

curl
curl https://api.lumavox.ai/v1/assets/delete \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "asset_ids": ["ast_a1B2c3D4e5F6g7H8", "ast_Z9y8X7w6V5u4T3s2"] }'

It answers {"affected": n}: how many were deleted. Ids that are not yours, or already deleted, are skipped.

Folders organise the library into a tree. Image, video, skybox, stored-audio and template requests take a folder_id to file their result straight into one.

Call Scope What it does
GET /v1/asset-folders assets One level of the tree, alphabetically: the top level, or the children of parent_id. Each folder has asset_count and folder_count.
GET /v1/asset-folders/{folder_id} assets One folder, with its ancestors from the top down — a breadcrumb trail.
POST /v1/asset-folders assets:write Create a folder: name (1–120 characters) and an optional parent_id. Answers 201.
PATCH /v1/asset-folders/{folder_id} assets:write Rename it, or move it: parent_id left out keeps it, null moves it to the top level.
DELETE /v1/asset-folders/{folder_id} assets:write Remove the folder. Its contents move up to its parent — nothing else is deleted. Answers how many moved: {"moved_assets": 12, "moved_folders": 1}.
curl
curl https://api.lumavox.ai/v1/asset-folders \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Campaign — spring" }'
Response — 201
{
"id": "fld_a1B2c3D4e5F6g7H8",
"name": "Campaign — spring",
"parent_id": null,
"created_at": "2026-10-24T09:50:00.000Z",
"asset_count": null,
"folder_count": null
}

asset_count and folder_count are counted only when you read folders; a create or an edit answers them null.

The rules of the tree:

  • Two folders side by side cannot share a name (409).
  • Folders nest at most 40 deep, and one level holds at most 500 folders — so a level’s listing is always one page, with next_cursor null.
  • A folder cannot be moved inside itself.