Files
Sending a file takes two steps: upload it, then post a message that attaches it. Reading one is a single download call. Both work with personal tokens and app tokens.
| Step | Operation | Scope |
|---|---|---|
| Upload | POST /v1/orgs/{orgId}/files |
files:write |
| Attach | POST /v1/channels/{channelId}/messages with attachment_ids |
messages:write |
| Download | GET /v1/attachments/{attachmentId}/content |
messages:read |
Upload a file
Send multipart/form-data with one part named file. Missived keeps the part's file name and content type.
curl -X POST https://api.missived.com/v1/orgs/<orgId>/files \
-H "Authorization: Bearer $MISSIVED_TOKEN" \
-F "[email protected];type=text/plain"
The answer is 201 with the stored file:
{
"id": "…",
"file_name": "build.log",
"content_type": "text/plain",
"size": 18234,
"created_at": "2026-10-18T09:12:00.000Z",
"download_url": "https://api.missived.com/v1/attachments/…/content"
}
The file is now pending: stored, but not in any channel yet.
- Size: at most 25 MB per file through the API (bigger files can still be sent from the app). Larger uploads answer
413 payload_too_large. - Types: the same file types the app's composer accepts: common images, video, audio, PDF, plain text, Markdown, CSV, Word, Excel and PowerPoint documents, and zip, tar and gzip archives. Anything else answers
415 unsupported_media_type, as does a body that isn't multipart. An empty file answers400 invalid_request. - Storage: the file counts against the org's storage. When it's full, the upload answers
413 storage_full. - At rest: files are stored encrypted.
Attach it to a message
Post a message with the upload's id in attachment_ids:
curl -X POST https://api.missived.com/v1/channels/<channelId>/messages \
-H "Authorization: Bearer $MISSIVED_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: build-1234-log" \
-d '{"content": "Build 1234 log", "attachment_ids": ["<fileId>"]}'
- Up to 10 files per message.
- Only files you uploaded (or, for an app token, that the app's bot uploaded), to the same org, that aren't attached to a message yet. Anything else answers
400 invalid_request. contentis optional when the message has files.- Once attached, a file belongs to that message and channel; it can't be attached again.
Download an attachment
Messages list their files in attachments. Each one has:
| Field | What it is |
|---|---|
id, file_name, content_type, size |
The file. size is in bytes. |
download_url |
The full URL of /v1/attachments/{id}/content, fetched with your own token. Apps download this way. |
url |
A short-lived link signed for the person the token acts as (relative to the API host). null for apps. |
Fetch the bytes with your token:
curl -L -o build.log \
-H "Authorization: Bearer $MISSIVED_TOKEN" \
https://api.missived.com/v1/attachments/<attachmentId>/content
The answer is the file, decrypted, with Content-Disposition: attachment and the file's name.
Who may download it:
- An attached file: anyone whose token may read that channel's messages. For an app, that means a channel it was added to (or any channel it can see, with
messages:read:all), and never a direct message or group chat. - A pending upload: only its uploader.
- Anything else, including files in other orgs, answers
404 not_found.
With the TypeScript SDK
The @missived/bot SDK has helpers for both steps:
import { RestClient } from '@missived/bot';
const rest = new RestClient({ token: process.env.MISSIVED_TOKEN! });
// Upload, then attach.
const file = await rest.uploadFile('<orgId>', { name: 'build.log', type: 'text/plain', data: logBytes });
await rest.createMessage('<channelId>', { content: 'Build log', attachment_ids: [file.id] });
// Download a message's first attachment.
const { data, fileName, contentType } = await rest.downloadAttachment(message.attachments[0].id);
data accepts a Blob, an ArrayBuffer or a Uint8Array. downloadAttachment returns the bytes as a Uint8Array, with the content type and the file name from the response.