Skip to main content

Create a file upload

POST 

/asset.create

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, supply media for a story, or host an avatar image. Unlike /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". For avatars, pass purpose: "avatar" and size (max 10 MiB). The response is an upload instruction: assetId, uploadUrl, uploadMethod, and uploadHeaders. Avatar responses also include imageUrl.
  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, 512×512 WebP for avatars) happens automatically once the bytes land. There is no separate "complete" call.
  4. Once the asset is ready, use it:

A freshly-uploaded asset may take a few seconds to process (videos take longer). Chat and story endpoints that consume the asset return a 400 with a "still processing" message until processing completes. Avatar imageUrl 404s until the image is ready — wait a moment after the upload returns before posting it.

The uploadUrl is short-lived; if it expires, call asset.create again for a fresh instruction. Maximum file size is 5 GiB for file / story, and 10 MiB for avatar.

Purposes

PurposeUseAccess
file (default)Chat message attachmentsOrganization and Personal
storyStory media (photo or video)Personal only
avatarsender.imageUrl and On-Air hosts.imageUrlOrganization and Personal

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 within about 23 hours of creating the asset; after that the asset is rejected and a new one must be created.

Avatar assets are public 512×512 WebP images. They do not expire. From API version 2026-08-25, sender.imageUrl and On-Air hosts.imageUrl must be a Roam-hosted avatar URL (this imageUrl, or a legacy /card-images/ or /photos/people/ URL). Third-party image URLs return 400. See API Versioning and Sender Profiles.

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"; any of item:write, chat:send_message, chat:write, or onair:write for purpose: "avatar".


OpenAPI Spec: chat-v1.json

Request

Responses

Upload instruction created.