# Create a file upload

`POST /asset.create`

## Description

Create a file asset and get back a self-describing instruction for
uploading its bytes — the JSON-friendly way to attach a file (image, PDF,
document, …) to a message, or to supply media for a story. Unlike
[`/item.upload`](/docs/api/item-upload), which takes raw bytes in the
request body, every caller-visible step here is JSON in / JSON out (so it
can be driven from MCP and other tool-calling clients), and the file bytes
never pass through this API.

**Flow:**
1. `POST /asset.create` with the file `name` (include the extension, e.g.
   `photo.png`) and, if known, its `size` in bytes. For stories, also pass
   `purpose: "story"`. The response is an upload instruction: `assetId`,
   `uploadUrl`, `uploadMethod`, and `uploadHeaders`.
2. Upload the raw bytes in a **single request**: use `uploadMethod` (a
   `POST`) against `uploadUrl`, send every header from `uploadHeaders`
   verbatim, and put the file in the request body. Send the headers exactly
   as given — they authorize the upload and select the single-request
   upload protocol; omitting any will cause the upload to fail.
3. Processing (thumbnails, previews, …) happens automatically once the
   bytes land. There is no separate "complete" call.
4. Once the asset is ready, use it:
   - `purpose: "file"` (default) — attach via `assetIds` on
     [`/chat.post`](/docs/api/chat-post) or
     [`/chat.update`](/docs/api/chat-update)
   - `purpose: "story"` — post via [`/story.post`](/docs/api/story-post)

A freshly-uploaded asset may take a few seconds to process (videos take
longer). Endpoints that consume the asset return a 400 with a "still
processing" message until processing completes.

The `uploadUrl` is short-lived; if it expires, call `asset.create` again for
a fresh instruction. Maximum file size is 5 GiB.

## Purposes

| Purpose | Use | Access |
|---------|-----|--------|
| `file` (default) | Chat message attachments | Organization and Personal |
| `story` | Story media (photo or video) | Personal only |

Story assets are owned by the authenticated user (stories are posted as you,
not as a bot) and expire about 48 hours after creation. Because the media
must outlive the story's 24-hour lifetime, call
[`/story.post`](/docs/api/story-post) within about 23 hours of creating the
asset; after that the asset is rejected and a new one must be created.

**Access:** Organization and Personal. `purpose: "story"` is Personal only.

**Required scope:** `item:write` for `purpose: "file"`; `chat:send_message`
or `chat:write` for `purpose: "story"`.

---

**OpenAPI Spec:** [chat-v1.json](https://developer.ro.am/chat-v1.json)

## Authentication

```
Authorization: Bearer YOUR_API_KEY
```

## Request Body

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `name` | string | Yes | File name, including its extension (e.g. `report.pdf`). Processing determines the media type from the extension. |
| `size` | integer | No | File size in bytes, if known. The true size is enforced server-side during the upload. Maximum 5 GiB.  |
| `purpose` | "file" | "story" | No | What the asset will be used for. `file` (default) for chat message attachments; `story` for story media (Personal tokens only).  |

## Responses

### 200 - Upload instruction created.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `assetId` | string | Yes | ID of the created asset. Pass it to chat.post / chat.update via `assetIds`, or to story.post, once the upload completes.  |
| `uploadUrl` | string | Yes | URL to upload the file bytes to. |
| `uploadMethod` | string | Yes | HTTP method to use for the upload request (always `POST`). |
| `uploadHeaders` | object | Yes | Headers to send verbatim on the upload request. They authorize the upload and select the single-request upload protocol.  |


#### Example Response

```json
{
  "assetId": "9b1c2d3e-4f50-6a7b-8c9d-0e1f2a3b4c5d",
  "uploadUrl": "https://uploads.ro.am/",
  "uploadMethod": "POST",
  "uploadHeaders": {
    "Authorization": "Bearer eyJhbGciOiJF...",
    "Upload-Draft-Interop-Version": "6",
    "Upload-Complete": "?1",
    "Upload-Length": "248173"
  }
}
```

### 400 - Bad request. Common causes:
- `name` missing or too long
- `size` negative or larger than 5 GiB
- `purpose: "story"` used with an organization token
- Malformed JSON body


### 401 - Presented invalid authentication credentials.

### 403 - The token lacks the required scope (`item:write` for file assets;
`chat:send_message` / `chat:write` for story assets).


### 405 - An unsupported method was requested.

### 500 - An internal error occurred.

---

*Machine-readable API documentation.*
*Full documentation: https://developer.ro.am/docs/api/asset-create*
