Assets
Stored codai Studio media — list, fetch a fresh signed URL, download or delete the images, music and videos you generated.
An asset is a generated file the gateway keeps for you: every image, music clip or video produced with response_format: "url", and every finished video job (the MP4 is always stored). Assets belong to the user that owns the API key — another user's asset id is a 404 — and are kept for 90 days.
Instead of base64 bytes you get an asset object with a signed URL valid for 1 hour. The URL needs no API key, so you can hand it to a browser, an <audio>/<video> element or a download tool. When it expires, ask for a fresh one.
Agents browse the library through the MCP server tools studio_assets_list (kind, before, limit) and studio_renders_list (before, limit, root_render_id). Both are read-only and never return signed URLs; generating media costs money and is not available through them.
The asset object
{
"asset_id": "7d0f2c1e-4b8a-4c1e-9a51-0f3c2e8d9b10",
"kind": "video",
"status": "ready",
"model": "veo-3.1-fast-generate-001",
"url": "https://storage.googleapis.com/…&X-Goog-Signature=…",
"mime_type": "video/mp4",
"bytes": 1834221,
"duration_seconds": 4,
"width": null,
"height": null,
"created_at": "2026-10-10T08:00:00.000Z",
"expires_at": "2026-10-10T09:00:00.000Z"
}| Field | Meaning |
|---|---|
asset_id | Stable UUID. Use it to get a fresh URL or delete the asset. |
kind | music, video, image, speech, render, upload or sfx. |
status | pending, ready, failed or deleted. Only ready assets have a url. |
model | Upstream model that produced it. |
url | Signed URL of the bytes, valid for 1 hour; null unless ready. Never cache it past expires_at. |
mime_type, bytes | Content type and size of the stored object. |
duration_seconds | Audio or video length; null for images. |
width, height | Pixels, when known; otherwise null. |
created_at | Creation time. The asset is removed 90 days later. |
expires_at | When url stops working; null when there is no URL. |
Endpoints
| Method | Path | Result |
|---|---|---|
GET | /v1/assets?kind=&before=&limit= | { object: "list", data: [asset…], next_before }, newest first |
GET | /v1/assets/{id} | One asset with a fresh signed url |
GET | /v1/assets/{id}/content | 302 redirect to the signed URL |
DELETE | /v1/assets/{id} | { id, object: "asset", deleted: true } |
Listing
kind filters by kind, limit is 1–100 (default 30). To page, pass the previous response's next_before as before until next_before is null.
curl "https://ai.codai.ro/v1/assets?kind=video&limit=20" \
-H "Authorization: Bearer $CODAI_API_KEY"Fetching the bytes
GET /v1/assets/{id} returns the asset with a newly signed URL. GET /v1/assets/{id}/content does the same and redirects to it, which is handy with curl -L. The redirect carries cache-control: private, no-store. An asset with no content yet (pending) is a 404 on /content.
curl -L -o plane.mp4 "https://ai.codai.ro/v1/assets/$ASSET_ID/content" \
-H "Authorization: Bearer $CODAI_API_KEY"Deleting
DELETE /v1/assets/{id} removes the stored file and marks the asset deleted; URLs already handed out stop working. Deleting does not refund the generation.
Creating assets
Ask for response_format: "url" on any media endpoint (see Embeddings, audio & realtime):
POST /v1/images/generationsandPOST /v1/images/edits— body field;POST /v1/audio/music— body field;GET /v1/videos/{id}— query parameter?response_format=url.
The default stays b64_json, so existing clients see no change. Videos are billed exactly once and their MP4 is stored whichever format you poll with.
Every finished Studio render is stored as an asset too and appears under kind=render (GET /v1/assets?kind=render).
Sound effects imported from the free CC0 sound-effects library are assets of kind=sfx.
Errors
| HTTP | code | When |
|---|---|---|
| 400 | bad_request | Invalid kind, before (must be an ISO date-time) or limit. |
| 404 | not_found | Unknown id, another user's asset, or /content on an asset without content. |
| 503 | assets_unavailable | Asset storage is not configured on this gateway. |
SDKs
import { Codai } from 'codai-sdk';
const client = new Codai({ apiKey: process.env.CODAI_API_KEY! });
const { assets } = await client.music.generate({ prompt: 'calm lo-fi piano', response_format: 'url' });
const url = await client.assets.contentUrl(assets[0]!.asset_id); // fresh 1 h URL
for await (const a of client.assets.iterate({ kind: 'video' })) console.log(a.asset_id, a.bytes);
await client.assets.delete(assets[0]!.asset_id);from codai import Codai
client = Codai(api_key=os.environ["CODAI_API_KEY"])
r = client.music.generate({"prompt": "calm lo-fi piano", "response_format": "url"})
url = client.assets.content_url(r.assets[0]["asset_id"])
for a in client.assets.iterate(kind="video"):
print(a["asset_id"], a["bytes"])
client.assets.delete(r.assets[0]["asset_id"])Both SDKs also have list(...), retrieve(id), content(id) (downloads the bytes from the signed URL) and contentPath(id) / content_path(id) (the authenticated redirect URL).