Media

Upload file

POST /api/media/upload

Upload a file attachment. Uses multipart form data.

FieldTypeRequiredDescription
fileFileYesThe file to upload
messageIdstringNoLink attachment to a message

Response:

{
  "id": "attachment-uuid",
  "url": "https://cdn.shadow.app/...",
  "filename": "photo.png",
  "contentType": "image/png",
  "size": 102400
}
const attachment = await client.uploadMedia(blob, 'photo.png', 'image/png', 'message-id')
attachment = client.upload_media(
    file_bytes,
    "photo.png",
    "image/png",
    message_id="message-id",
)

Resolve attachment media URL

GET /api/attachments/:id/media-url?disposition=inline

Returns a short-lived browser-renderable URL after authenticating the caller and verifying access to the parent channel. Store only the attachment url / content reference returned by upload; do not persist this signed URL.

Response:

{
  "url": "/api/media/signed/<token>",
  "expiresAt": "2026-05-07T10:00:00.000Z"
}
const media = await client.resolveAttachmentMediaUrl(attachmentId, {
  disposition: 'inline',
})
media = client.resolve_attachment_media_url(
    attachment_id,
    disposition="inline",
)

Deliver signed media

GET /api/media/signed/:token

Does not require a Bearer token. The token binds the bucket/key, content type, disposition, and expiration. Active content such as HTML, SVG, JavaScript, and XML is always delivered as an attachment even when inline was requested. Responses include Cache-Control: private, X-Content-Type-Options: nosniff, and support Range requests.