encrypt_stream / decrypt_stream), upload via the /2/chat/media/upload routes (sidebar API reference → Media), then attach media_hash_key on encrypt_message.
Include media.write with your DM scopes when uploading. Use hyphenated conversation ids in paths (: → -). Prefer MIME/dimensions from decrypted bytes.
This path is not the Posts media model (expansions=attachments.media_keys, media.fields=variants, etc.). Those parameters apply to Posts; E2EE X Chat blobs are addressed by media_hash_key and X Chat media download.
Encrypt
- Python
- TypeScript
- Rust
- Go
- C#
- Java
encrypt_stream / decrypt_stream process the whole payload in memory. For large files, stream_encryptor() / stream_decryptor() return incremental objects (StreamEncryptor / StreamDecryptor): feed chunks with push, then call finish once—finish errors if the stream was truncated.
Upload
Use the request bodies on the OpenAPI pages under API reference → Media. Prefer encrypted blob size where size is required. Finalize yields
media_hash_key for attachments and download. Retry transient 5xx with backoff. Python/TypeScript may use the XDK when media helpers exist; otherwise POST with a Bearer token in any language.
Send with an attachment
Encrypt with a media attachment, then POST the send-message body (same field mapping as Getting Started).- Python
- TypeScript
- Rust
- Go
- C#
- Java
Download and decrypt
Path:GET /2/chat/media/{conversation_id}/{media_hash_key}. Response body is ciphertext. On inbound messages, read media_hash_key from decrypted attachments / media_hashes.
Pick the key by the event’s key version. Each decrypted message event carries the keyVersion (JS; key_version in the other bindings) its content was encrypted under. Decrypt an attachment with the conversation key for that version—conversationKeys.keys[event.keyVersion]—not the latest. After a key rotation (for example a member add), the latest key cannot decrypt media attached to older messages.
- Python
- TypeScript
- Python
- TypeScript
- Rust
- Go
- C#
- Java
Tips
- Use the same conversation key version as when the media was encrypted
- Do not log plaintext media or raw keys
- Detect MIME after decrypt
- Web clients: encrypt/decrypt on the client when possible; keep OAuth tokens on your server