---
title: document_create_upload
description: Reserve a file against a brand and get a short-lived URL to upload the bytes to.
---

Reserves a **document** against a brand and returns a short-lived URL to `PUT` the bytes to. **The bytes never pass through AdCrunch** — your client uploads them directly to storage, which is why a large file is possible at all.

Three steps, always in this order:

1. `document_create_upload` — reserve, and get an `uploadUrl`.
2. `PUT` the file to that URL yourself.
3. [`document_finalize`](/mcp/tools/document-finalize) — tell us it landed.

## Input

| Field          | Type     | Required | Description                          |
| -------------- | -------- | -------- | ------------------------------------ |
| `brand_name`   | `string` | yes      | The brand slug this file belongs to. |
| `filename`     | `string` | yes      | The original filename, for display.  |
| `content_type` | `string` | yes      | What you're about to upload.         |

Accepted types are PDFs and images: `application/pdf`, `image/png`, `image/jpeg`, `image/gif`, `image/webp`. SVG is **not** accepted — it can carry script, and these files are served from an adcrunch.dev host.

What you declare here is only a declaration. The real check happens at [`document_finalize`](/mcp/tools/document-finalize), against the file that actually arrived.

## Output

`{ documentId, uploadUrl, expiresInSeconds }`.

## Example

```ts
const { documentId, uploadUrl } = document_create_upload({
  brand_name: 'acme-running',
  filename: 'brand-guidelines.pdf',
  content_type: 'application/pdf',
});
// then: PUT the file to uploadUrl, then document_finalize({ document_id: documentId })
```

:::warning[The URL expires]

The upload URL is valid for 15 minutes and grants exactly one write. If it expires before you finish, reserve again — the abandoned reservation is cleaned up automatically.

:::

Requires the `brand:write` scope.

## Reference

Reserve a document against a brand and get a short-lived URL to upload the bytes to. Upload the file yourself with an HTTP PUT to that URL, then call document_finalize. The bytes never pass through this tool — only your runtime touches them.

### Input

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `brand_name` | string | yes | The brand slug this document belongs to. |
| `content_type` | string | yes | The file's content type. Accepted: application/pdf, image/gif, image/jpeg, image/png, image/webp. |
| `filename` | string | yes | The original filename, for display. |

### 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.

- `not_found`
- `unsupported_type`
- `forbidden`
- `invalid_request`
- `internal_error`

### Scope

The token must hold `brand: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.
