# Asset tools

API reference for the ZenCreator MCP tools that **upload** media and **fetch URLs** for generated or uploaded assets. Every asset is identified by an `asset_id` (a UUID). You feed `asset_id`s into generation tools, and you read them back out of completed tasks.

> **Where asset ids come from.** Generation output ids are discovered with `zencreator_get_call_result` after a task finishes — see [generation tools](../tools/generation.md). To turn user-supplied media into an `asset_id`, use `zencreator_upload_asset` below. For end-to-end recipes, see [workflows](../workflows.md).

## Download vs. preview — read this first

Two different URLs come back from this tool group, and they are **not interchangeable**:

| | `download_url` | `preview_url` |
| --- | --- | --- |
| What it is | The **full-resolution original** binary | A **downscaled, re-encoded web preview** (thumbnail) |
| Format | The original's `media_type` (e.g. `image/png`, `video/mp4`) | The preview's own `preview_media_type` (commonly `image/webp`) — usually **differs** from the original |
| Use it for | Downloading / saving the real file | Viewing / rendering in chat |
| Tool | `zencreator_get_asset_download_url` / `_urls` | `zencreator_get_asset_preview_url` / `_urls` |

Never present a `preview_url` as "the original" or "full resolution". When the user wants to save the file, call the download tool and label it with `media_type`. When you just want to show the image, call the preview tool and label it with `preview_media_type`.

All URLs returned here are **signed and expire within a few minutes**. Do not cache them long-term; re-call the tool to get a fresh one.

Every tool also accepts `response_format` (`"markdown"` default, or `"json"`) and always returns a full `structuredContent` object regardless of format. See [concepts](../concepts.md) for the shared response contract.

---

## zencreator_upload_asset

Create a new ZenCreator asset from a URL or base64 bytes, returning an `asset_id` you can pass to generation tools that operate on user-supplied media (image-to-video, lipsync, face-swap, image editing, upscaler, photoshoot with reference, and so on). Without an upload, those generators cannot run on the user's own images.

Provide **exactly one** of `source_url` or `data_base64`.

- The user pasted a public `http(s)://` URL → use `source_url`. **Preferred:** the server fetches the full-resolution bytes itself, with no tool-argument size limit.
- The user gave a local file path (Claude Code, CLI clients) → read the real bytes yourself and use `data_base64`.
- The user pasted a small RFC 2397 `data:` URL → use `source_url`. The base64 rides in the request body, so this works only for small images.
- The user attached media to the chat (Claude.ai web / mobile app, ChatGPT, Cursor, Claude Desktop) → this usually **cannot** be uploaded via `data_base64`. The attachment is a vision block, not copyable bytes; `data_base64` only fits tiny images, and the request body is capped (~25 MB over the HTTP transport). **Never resize, downscale, crop, or re-encode the user's image to squeeze it into a tool argument — that silently destroys their photo.** Instead ask the user for a public `http(s)://` URL, or to upload the file in the ZenCreator web app and supply the `asset_id`. Chat-attachment URLs are session-bound and unreachable from the server — do not try to refetch them.

**Parameters**

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `source_url` | string | one of `source_url`/`data_base64` | — | `http(s)://` URL or RFC 2397 `data:` URL. The server fetches the bytes and forwards them as a multipart upload (max 50 MB, 30 s fetch timeout). `file://`, `blob:`, `javascript:` and other schemes are rejected. |
| `data_base64` | string | one of `source_url`/`data_base64` | — | Raw file bytes as a base64 string. Use for **local file paths** (Claude Code / CLI). Inline base64 rides in the request body (capped at ~25 MB over the HTTP transport — roughly an ≤18 MB image after base64 inflation), so it is not viable for large files or for chat attachments. Prefer `source_url` whenever a URL exists. |
| `filename` | string | No | — | Up to 255 chars. Used to detect the media type via extension and as the multipart `filename`. Auto-derived from a `source_url` path when absent. |
| `media_type` | enum | No | — | Explicit override; required only when auto-detection fails. One of the [allowed media types](#allowed-media-types) below. |
| `response_format` | string | No | `"markdown"` | `"markdown"` or `"json"`. |

**Media-type detection precedence** (first match wins): explicit `media_type` arg → HTTP `Content-Type` from `source_url` → media type from the `data:` URL header → `filename` / URL pathname extension. If none yields an allowed value, the tool errors and asks you to pass `media_type` explicitly.

**Returns** — `structuredContent`:

```json
{
  "asset_id": "string",   // pass to generation tools as image_asset_id / video_asset_id / etc.
  "media_type": "string", // one of the allowed list
  "size_bytes": 0          // local byte count before upload
}
```

**Limits**

- 50 MB per upload (applied locally to all media types). This full cap is reachable via `source_url` (fetched server-side); for inline `data_base64` over the HTTP transport the effective ceiling is the ~25 MB request-body cap (roughly an ≤18 MB image after base64 inflation).
- For `image/*` the backend additionally validates that the bytes are a real, decodable image and may reject with a 400 even if the mime passes.

**Not idempotent** — every call creates a new asset row. Calling twice with the same bytes produces two distinct `asset_id`s.

**Errors**

- `pass exactly one of 'source_url' or 'data_base64'` — provide one input mode, not zero or both.
- `unsupported scheme` / scheme blocked — use `http(s)://` or `data:` only.
- `malformed data: URL` — the `data:` URL could not be parsed.
- `data_base64 is not valid base64` / `decoded to zero bytes` — fix the encoding.
- payload `> 50 MB cap` — resize or compress before uploading.
- `could not determine an allowed media_type` — pass an explicit `media_type`.
- 400 from backend — corrupt image, oversized image, or a mime that doesn't match the bytes.
- 401 — the caller must re-authorize.

**Example**

> "Here's a photo at this URL — upload it and use it as the reference for the next generation." (The client calls `zencreator_upload_asset` with `source_url`; the server fetches the full-resolution bytes.) For a raw chat attachment with no URL, ask the user for a public link or to upload it in the ZenCreator web app — never downscale it to fit a tool argument.

### Allowed media types

`zencreator_upload_asset`'s `media_type` enum and the upload validator accept exactly:

```
image/png   image/jpeg  image/jpg   image/webp  image/heic  image/heif
video/mp4   video/mov   video/quicktime
audio/mpeg  audio/mp3   audio/wav   audio/x-wav  audio/x-m4a
```

Anything else is rejected.

---

## zencreator_get_asset

Return an asset's media type so you know how to handle it before fetching the binary.

**Parameters**

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asset_id` | string (UUID) | Yes | — | The asset UUID. |
| `response_format` | string | No | `"markdown"` | `"markdown"` or `"json"`. |

**Returns** — `structuredContent`:

```json
{
  "id": "string",
  "media_type": "string"  // RFC 6838 media type, e.g. 'image/png'
}
```

**Errors**

- 404 — `asset_id` not recognised or not owned by the user.

---

## zencreator_get_asset_download_url

Return a short-lived presigned URL for the **full-resolution original** binary, plus the asset's media type so you can state the file format to the user.

Always tell the user the format — never hand over a bare URL. For 2+ assets, prefer the batch variant `zencreator_get_asset_download_urls`.

**Parameters**

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asset_id` | string (UUID) | Yes | — | The asset UUID. |
| `response_format` | string | No | `"markdown"` | `"markdown"` or `"json"`. |

**Returns** — `structuredContent`:

```json
{
  "asset_id": "string",
  "download_url": "string", // signed; expires within minutes
  "media_type": "string"    // RFC 6838 type of the original, e.g. 'image/png', 'video/mp4'
}
```

**Errors**

- 404 — `asset_id` not recognised or not owned by the user.
- The URL expires shortly — if it's stale, re-call to get a fresh one.

**Example**

> "Give me the download link for that image." → returns `download_url` + `media_type` to surface as "Here's your image (PNG): &lt;url&gt;".

---

## zencreator_get_asset_download_urls

Batch version of `zencreator_get_asset_download_url`: fetch full-resolution download URLs and media types for **1..50** assets in one round-trip. Use whenever a task produced 2+ assets. Per-asset failures do not abort the batch.

**Parameters**

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asset_ids` | array&lt;string UUID&gt; | Yes | — | 1 to 50 asset UUIDs. |
| `response_format` | string | No | `"markdown"` | `"markdown"` or `"json"`. |

**Returns** — `structuredContent`:

```json
{
  "count": 0,
  "succeeded": 0,
  "failed": 0,
  "items": [
    {
      "asset_id": "string",
      "download_url": "string", // present iff succeeded; signed and expires shortly
      "media_type": "string",   // RFC 6838; present iff succeeded
      "error": "string"          // present iff failed (e.g. backend 404)
    }
  ]
}
```

**Errors**

- Per-asset: each failed item carries an `error` string; the rest still succeed.
- URLs are signed and expire within a few minutes — surface them promptly or re-call.

---

## zencreator_get_asset_preview_url

Return a short-lived URL to a **reduced-size, re-encoded web preview** (a downscaled thumbnail — commonly WebP, **not** the original), the original asset's media type, and — by default — the preview bytes **inline** as an MCP image content block so sandboxed clients (e.g. Claude.ai web) can render the image without reaching storage directly.

`preview_url` is for **viewing**, not downloading. Label it with `preview_media_type` and state the original's `media_type` separately. For the full-resolution original, use `zencreator_get_asset_download_url`. For 2+ assets, prefer `zencreator_get_asset_preview_urls`.

**Parameters**

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asset_id` | string (UUID) | Yes | — | The asset UUID. |
| `inline` | boolean | No | `true` | When `true`, fetch the preview bytes and attach them as an image content block. Per-image inline cap is 2 MB; over that it falls back to URL-only with an `inline_skipped` note. Set `false` for a smaller response or when the client renders URLs natively. |
| `response_format` | string | No | `"markdown"` | `"markdown"` or `"json"`. |

**Returns** — `structuredContent`:

```json
{
  "asset_id": "string",
  "preview_url": "string",        // reduced-size web preview — NOT the original
  "preview_media_type": "string", // RFC 6838 type of preview_url itself, e.g. 'image/webp' (omitted if undeterminable)
  "media_type": "string",         // RFC 6838 type of the ORIGINAL asset, e.g. 'image/png'
  "inline_skipped": "string"      // present only when inline was requested but skipped
}
```

When inlining succeeds, the response also carries an extra `{ type: "image", mimeType, data }` content block, which most clients render as an inline thumbnail.

**Errors**

- 404 — `asset_id` not recognised or not owned by the user.
- `inline_skipped` is informational (e.g. image &gt; 2 MB, or a network error fetching the preview) — the URL is still returned.

**Example**

> "Show me that result in the chat." → returns `preview_url` + inlined thumbnail; tell the user "Preview (WebP), shown inline; original is PNG — say the word for the full-res link."

---

## zencreator_get_asset_preview_urls

Batch version of `zencreator_get_asset_preview_url`: fetch reduced-size web preview URLs plus each original's media type for **1..50** assets in one round-trip, and — by default — inline the preview bytes as image content blocks. Use whenever a task produced 2+ assets. Per-asset failures do not abort the batch.

These URLs are for **viewing**, not downloading. For full-resolution originals use `zencreator_get_asset_download_urls`.

**Parameters**

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asset_ids` | array&lt;string UUID&gt; | Yes | — | 1 to 50 asset UUIDs. |
| `inline` | boolean | No | `true` | Attach preview bytes as image content blocks. Per-image cap 2 MB; per-call total cap 8 MB. Assets exceeding either cap fall back to URL-only with an `inline_skipped` note. Set `false` to keep the response small. |
| `response_format` | string | No | `"markdown"` | `"markdown"` or `"json"`. |

**Returns** — `structuredContent`:

```json
{
  "count": 0,     // input count
  "succeeded": 0, // URL fetched
  "failed": 0,    // URL fetch failed (no preview_url, has error)
  "inlined": 0,   // bytes also attached as image content blocks
  "items": [
    {
      "asset_id": "string",
      "preview_url": "string",        // web preview — NOT the original; present iff URL fetch succeeded
      "preview_media_type": "string", // RFC 6838 type of preview_url itself (omitted if undeterminable)
      "media_type": "string",         // RFC 6838 type of the ORIGINAL asset; present iff succeeded
      "inline_skipped": "string",     // present iff URL ok but inlining was skipped
      "error": "string"               // present iff URL fetch failed
    }
  ]
}
```

The response carries one `{ type: "image", mimeType, data }` content block per successfully inlined asset, in the same order as `items`.

**Errors**

- Per-asset: failed items carry an `error`; inlining failures carry `inline_skipped`. The rest still succeed.
- The per-call 8 MB inline budget: once reached, remaining assets fall back to URL-only with `inline_skipped: "batch ... cap reached"`.

---

## See also

- [Generation tools](../tools/generation.md) — submit generations and discover output `asset_id`s with `zencreator_get_call_result`.
- [Workflows](../workflows.md) — end-to-end upload → generate → fetch recipes.
- [Concepts](../concepts.md) — the shared response, pagination, and credits contracts.
