# Managed Assets

> Upload images, video, and audio so you can reference them by URL in edit and generation calls.

The edit and video endpoints accept **public URLs**, not raw bytes. If a file only exists on your device or a private server, upload it here first — the returned `url` is immediately usable as an `image_urls` entry on `/v1/images/edit` or as `image_url` on `/v1/videos/generate`.

Every upload gets a durable `id`, so you can list what you've uploaded, re-fetch a URL, and delete assets you no longer need.

> [!NOTE]
> Creating and deleting an asset requires the **`uploads:write`** scope. Listing and fetching your assets require a valid API key. Grant the scope when creating or updating the key from the [API Keys](/docs/developer-tools/api-keys) dashboard.

## `POST /v1/assets`

Upload a file and receive a managed asset record with a CDN URL.

**Auth:** API Key (`uploads:write` scope)

**Content-Type:** `multipart/form-data`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file` | binary | Yes | Image, video, or audio file. |
| `metadata` | `string` (JSON) | No | An arbitrary JSON object of your own labels, stored alongside the asset. |

**Size limits**

| Kind | Max size |
| --- | --- |
| Image (`image/*`) | 20 MB |
| Video (`video/*`) | 50 MB |
| Audio (`audio/*`) | 15 MB |

**Response** `201 Created`

```json
{
  "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
  "url": "https://cdn.picxstudio.com/uploads/api/portrait.jpg",
  "kind": "image",
  "filename": "portrait.jpg",
  "content_type": "image/jpeg",
  "bytes": 245120,
  "metadata": { "source": "onboarding" },
  "created_at": "2026-08-20T09:15:00Z"
}
```

The `kind` field is inferred from the MIME type: `image`, `video`, or `audio`. Files with any other content type are rejected with `400`.

## Upload, then edit

**JavaScript SDK**

```js
import { PicX } from "picx-ai";

const picx = new PicX(process.env.PICX_API_KEY);

// 1. Upload the source image
const asset = await picx.assets.create({
  file: new Blob([bytes], { type: "image/jpeg" }),
  filename: "portrait.jpg",
});

// 2. Pass the URL to an edit call
const edited = await picx.images.edit({
  instruction: "turn this into a naive doodle avatar",
  image_urls: [asset.url],
});

console.log(edited.url);
```

**Python SDK**

```python
import os
from picx import PicX

picx = PicX(os.environ["PICX_API_KEY"])

# 1. Upload the source image
with open("portrait.jpg", "rb") as source:
    asset = picx.assets.create(file=source)

# 2. Pass the URL to an edit call
edited = picx.images.edit(
    instruction="turn this into a naive doodle avatar",
    image_urls=[asset.url],
)

print(edited.url)
```

**curl**

```bash
# Upload
curl -X POST https://api.picxstudio.com/v1/assets \
  -H "Authorization: Bearer pxsk_your_key" \
  -F "file=@portrait.jpg" \
  -F 'metadata={"source":"onboarding"}'

# Use the returned URL in an edit
curl -X POST https://api.picxstudio.com/v1/images/edit \
  -H "Authorization: Bearer pxsk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"instruction":"turn this into a doodle avatar","image_urls":["https://cdn.picxstudio.com/uploads/api/portrait.jpg"]}'
```

## Upload, then generate video

```js
const ref = await picx.assets.create({ file: keyframeBlob, filename: "keyframe.png" });

const video = await picx.video.create({
  prompt: "smooth camera push-in on this scene",
  model: "seedance-2.5",
  image_url: ref.url, // first-frame reference
});
```

## List assets

```bash
curl https://api.picxstudio.com/v1/assets?kind=image&limit=10 \
  -H "Authorization: Bearer pxsk_your_key"
```

Response:

```json
{
  "assets": [ /* AssetResponse[] */ ],
  "total": 42,
  "limit": 10,
  "offset": 0
}
```

Filter by `kind` (`image`, `video`, `audio`) and paginate with `limit` (1–100, default 20) and `offset`.

## Delete an asset

```bash
curl -X DELETE https://api.picxstudio.com/v1/assets/d290f1ee-6c54-4b01-90e6-d701748f0851 \
  -H "Authorization: Bearer pxsk_your_key"
```

Deletion is soft: the CDN URL remains valid so existing generations that reference it keep working, but the asset no longer appears in your list.

> [!TIP]
> If you only need a throwaway URL for one generation (no listing, no re-use), you can skip managed assets and host the file anywhere publicly accessible. Managed assets are for files you plan to reference across multiple calls or need to audit later.

## FAQ

### Does uploading cost credits?

No. Only generation and editing endpoints consume credits.

### Can I use data URIs instead of uploading?

No. The edit and video endpoints require HTTPS URLs — `data:` URIs are rejected. Upload the file first, then pass the returned `url`.

### What happens if I delete an asset that a generation references?

The CDN URL stays live. Deletion only removes the asset from your list; it does not purge the underlying object.

### Do I need `uploads:write` to list or fetch assets?

No. Any valid API key can list and fetch your assets. Only `create` and `delete` require the `uploads:write` scope.
