Skip to main content
Your bot can attach files to channel messages and DMs, and open the files humans post. Attachments are end-to-end encrypted: the blob is AES-256-GCM encrypted under a fresh per-file key before upload, the key travels as a conversation-wrapped grant the server cannot open, and the message itself just carries a [file:<id>:<name>] token inside its encrypted body on the ordinary send() path.
Attachments ride the REST lane (client.rest), which is off until the server operator enables bot REST credentials. Until then every call here rejects with the lane’s “disabled server-side” error. See The REST lane. The grant write also requires the upload_file permission in the target server.

Send a file to a channel

sendFile encrypts, uploads, writes the share grant, and only then sends the message. If the grant is refused it deletes the upload and rejects, so a message never references an attachment nobody can open. Thread sends work too: pass { threadId } in the options like any other send. The upload and grant legs are awaited and reject on failure. The final message send is fire-and-forget by default, like send(). Pass ack: true to await the server’s echo and get the persisted Message back; a denial pinned to that message then rejects with CloakActionError and the uploads are deleted, while an indeterminate outcome (CloakSendTimeoutError) leaves them, because the message may exist. Pass signal to cancel the upload and grant legs.

Send several files in one message

Each file is encrypted, uploaded, and granted in turn, and the message carries every [file:<id>:<name>] token, which is how the Cloak clients attach several files. If any upload or grant fails, every blob uploaded so far is deleted and the call rejects, so a partial delivery never lands. sendFilesDM(userId, files, opts) is the DM form, and sendFile() is sendFiles() with one file.

Send a file in a DM

Same shape, different grant: the key is wrapped under the pairwise DM key and granted to the recipient’s user id. First contact pays the same one-time setup as sendDM().

Receive and download

Incoming attachments are parsed out of the decrypted body and exposed on the message:
msg.attachments is parse-only: an entry proves the sender named a file, not that your bot may read it. Authorization happens at download time, against the grant the server holds.
Grants are epoch-tagged. downloadAttachment reads the epoch off the grant and acquires that epoch’s key when the keystore lacks it, so files shared before your bot’s current epoch still open. After a server root key cycle, older attachments are permanently unopenable. That is the rotation design, not a bug.

Limits and metering

Per-file and total-storage limits are the bot owner’s plan tier, not a bot-specific number, and bot transfers are metered by a rolling bandwidth window in both directions. The server’s verdict surfaces verbatim as a CloakRestError: A download can also fail with a “pre-060 raw-key grant” error: the file was shared by an outdated client using the legacy raw-key format, and its owner must re-share it. The SDK never produces raw-key grants itself.

Next

The REST lane

The credential these calls ride, and why it is off by default.

What is encrypted, and what is not

Where attachments sit among Cloak’s encryption lanes.

Crypto reference

fileCipher, parseFileKeyGrant, and the exported grant helpers.

Handling errors

CloakRestError and reading errorCode.