# Assets (/docs/gateway/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 [#the-asset-object]

```json
{
  "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 [#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 [#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`.

```bash
curl "https://ai.codai.ro/v1/assets?kind=video&limit=20" \
  -H "Authorization: Bearer $CODAI_API_KEY"
```

### Fetching the bytes [#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`.

```bash
curl -L -o plane.mp4 "https://ai.codai.ro/v1/assets/$ASSET_ID/content" \
  -H "Authorization: Bearer $CODAI_API_KEY"
```

### Deleting [#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 [#creating-assets]

Ask for `response_format: "url"` on any media endpoint (see [Embeddings, audio & realtime](./embeddings-audio.mdx#getting-a-url-instead-of-bytes)):

- `POST /v1/images/generations` and `POST /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](./renders.mdx) 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](./sfx.mdx) are assets of `kind=sfx`.

## Errors [#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 [#sdks]

```ts
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);
```

```python
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).
