---
title: asset_create_upload
description: Reserve an Asset and get a short-lived URL to upload the file to. Step one of bringing your own creative in.
---

Reserves an **Asset** and returns a short-lived, presigned URL to upload the file to. This is step one of the upload flow; see [Bring your own creative](/mcp/assets) for the whole sequence.

## Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `filename` | `string` | yes | The original filename, including extension. |

## Output

`{ assetId, uploadUrl, expiresAt }`:

| Field | Type | Description |
| --- | --- | --- |
| `assetId` | `ast_*` | The reserved Asset id — pass it to `asset_finalize`. |
| `uploadUrl` | `string` | A presigned `PUT` URL. Send the file bytes here. |
| `expiresAt` | `number` | Epoch ms when `uploadUrl` stops working. |

## The agent uploads the bytes itself

Send an HTTP `PUT` to `uploadUrl` with the file as the request body and its `Content-Type` set. The bytes must **never pass through the conversation** — the agent reads the file and PUTs it from its own runtime.

```
PUT <uploadUrl>
Content-Type: image/png

<the file bytes>
```

Once the `PUT` returns `200`, call [`asset_finalize`](/mcp/tools/asset-finalize).

:::warning[Requires outbound network access]

The `PUT` targets `*.r2.cloudflarestorage.com`. If the agent's runtime restricts network access, that host must be allowed or the upload can't complete.

:::

## Example

> _"Add this logo.png to our creative library."_

Claude calls `asset_create_upload({ filename: 'logo.png' })`, PUTs the file to the returned `uploadUrl`, then finalizes with the returned `assetId`.

## Errors

- The file's type and size aren't checked here — nothing is known until the bytes arrive, so rejection happens at [`asset_finalize`](/mcp/tools/asset-finalize).
- `401` or `403` — see [Errors](/mcp/errors), which every tool shares. This one needs `asset:write`.

## Reference

Reserve an Asset and get a short-lived URL to upload the file to.

**You upload the bytes yourself**: send an HTTP `PUT` to the returned `uploadUrl` with the file as the body and its `Content-Type` set. The bytes must never pass through this conversation — read the file and PUT it from your own runtime.

This requires your runtime to reach `*.r2.cloudflarestorage.com`; if network access is restricted, that host must be allowed. Once the PUT succeeds, call asset_finalize.

### Input

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `filename` | string | yes | The original filename, including its extension. |

### Failure codes

A failed call has `isError` set, and `structuredContent.error` holds one of these codes. [Errors](/mcp/errors) describes the shape of a failed call.

- `reservation_failed`
- `forbidden`
- `invalid_request`
- `internal_error`

### Scope

The token must hold `asset:write`. [Auth & scopes](/mcp/auth) lists each scope.

### Annotations

A client reads these hints. A hint that the tool does not declare has the default value of the MCP specification.

- **Writes.** The tool can change data.
- **Destructive.** The tool can make a change that you cannot undo. A client can ask you to confirm before it calls the tool.
- **Not idempotent.** A second call with the same arguments can change more.
- **Closed world.** The tool reads and writes the data of AdCrunch only.
