Message Routing API
Message routing is the core function of Cryptid servers. All messages are encrypted with MLS before reaching the server, making the server a “dumb pipe” that routes opaque ciphertext without understanding content, relationships, or cryptographic meaning.
The server’s only responsibilities are:
-
Basic format validation (message IDs, addresses)
-
Temporary queuing for offline recipients
-
Federation routing to remote servers
-
Rate limiting to prevent abuse
Send Message
Section titled “Send Message”Routes a single encrypted MLS message to a recipient. If the recipient is on the same server, the message is queued locally. If the recipient is on a different server (federated), the message is forwarded via the federation protocol.
Endpoint
Section titled “Endpoint”POST /v1/messages
Request
Section titled “Request”POST /v1/messages HTTP/1.1Authorization: Bearer {access_token}Content-Type: application/json
{ "message_id": "a1b2c3d4e5f61728394a5b6c7d8e9f10123456789abcdef", "recipient_address": "bob@server2.example.com", "group_id": "f1e2d3c4b5a6978869504132e1f2d3c4b5a6978869504132", "mls_ciphertext": "base64_mls_encrypted_payload_here", "sender_signature": "ed25519_signature_over_ciphertext", "timestamp": 1759697179, "message_type": "Text"}
Parameters:
message_id
: 32-byte hex message identifierrecipient_address
: Target device addressgroup_id
: 32-byte hex group identifiermls_ciphertext
: Base64 encoded MLS encrypted payload (server cannot decrypt)sender_signature
: Ed25519 signature over ciphertextmessage_type
:Text
,Media
, orSystemOperation
Response (202 Accepted)
Section titled “Response (202 Accepted)”{ "message_id": "a1b2c3d4e5f61728394a5b6c7d8e9f10123456789abcdef", "status": "queued", "delivery_estimate": "immediate"}
Response Fields:
status
:queued
: Message stored locally for retrieval by recipientfederated
: Message forwarded to recipient’s home server
delivery_estimate
:immediate
: Recipient is online or will retrieve the message soondelayed
: Recipient may be offline (message expires per retention policy)
Send Batch Messages
Section titled “Send Batch Messages”Sends multiple messages in a single request for efficiency.
This is particularly useful for:
-
MLS group operations (commit + welcome messages)
-
Multi-device message fanout (same content to multiple recipients)
-
Delivery receipt batching
Batch requests are atomic at the transport level but individual messages may have different statuses (some queued locally, others federated).
Why Batch?
Section titled “Why Batch?”MLS group operations often require sending multiple messages simultaneously (e.g., adding a member requires sending a commit to existing members and a welcome to the new number). Batching ensures these related messages are sent together and reduces HTTP overhead.
Max Batch Size: 100 messages per request (configurable by server)
Endpoint
Section titled “Endpoint”POST /v1/messages/batch
Request
Section titled “Request”POST /v1/messages/batch HTTP/1.1Authorization: Bearer {access_token}Content-Type: application/json
{ "messages": [ { "message_id": "message_1_id_hex", "recipient_address": "alice@server1.com", "group_id": "group_id_hex", "mls_ciphertext": "base64_ciphertext_1", "sender_signature": "signature_1", "timestamp": 1759697179, "message_type": "SystemOperation" }, { "message_id": "message_2_id_hex", "recipient_address": "bob@server2.com", "group_id": "group_id_hex", "mls_ciphertext": "base64_ciphertext_2", "sender_signature": "signature_2", "timestamp": 1759697179, "message_type": "SystemOperation" } ]}
Response (202 Accepted)
Section titled “Response (202 Accepted)”{ "accepted_count": 2, "rejected_count": 0, "message_statuses": [ { "message_id": "message_1_id_hex", "status": "queued" }, { "message_id": "message_2_id_hex", "status": "federated" } ]}
Retrieve Messages
Section titled “Retrieve Messages”Retrieves encrypted messages queued for the authenticated device. This endpoint is used for polling-based message delivery. For real-time delivery, use the WebSocket stream endpoint instead.
Messages remain in the queue until explicitly acknowledged. Unacknowledged messages are redelivered on subsequent polls until they expire (default: 30 days).
Endpoint
Section titled “Endpoint”GET /v1/messages
Query Parameters:
since
(optional): Unix timestamp for incremental synclimit
(optional): Max messages to return (default: 100, max: 1000)
Request
Section titled “Request”GET /v1/messages?since=1759697179&limit=50 HTTP/1.1Authorization: Bearer {access_token}
Response (200 OK)
Section titled “Response (200 OK)”{ "messages": [ { "message_id": "32_byte_message_id_hex", "group_id": "32_byte_group_id_hex", "mls_ciphertext": "base64_encrypted_content", "sender_signature": "ed25519_signature", "timestamp": 1758999498, "message_type": "Text", "received_at": 1758999500 } ], "has_more": false, "next_poll_interval": 30, "server_timestamp": 1758999520}
Response Fields:
-
messages[]
: Array of queued messages (oldest first) -
has_more
: Boolean indicating if more messages exceed the limit -
next_poll_interval
: Server’s recommended polling interval in seconds (typically 30-60s) -
server_timestamp
: Current server time for client synchronization
Polling Best Practices:
-
Use the
since
parameter for incremental sync (reduces bandwidth) -
Respect
next_poll_interval
to avoid rate limiting -
Consider WebSocket streaming for real-time applications
-
Acknowledge messages promptly to free server storage
Acknowledge Message
Section titled “Acknowledge Message”Permanently removes a message from the server queue after successful client-side decryption and storage. Once acknowledged, the message is deleted from the server and cannot be retrieved again.
When to Acknowledge:
-
After successful MLS decryption
-
After storing message in local database
-
Before displaying to user
When NOT to Acknowledge:
-
MLS decryption failed (may be retried later)
-
Message is invalid or corrupted
-
Local storage failed
Endpoint
Section titled “Endpoint”DELETE /v1/messages/{message_id}
Request
Section titled “Request”DELETE /v1/messages/a1b2c3d4e5f61728394a5b6c7d8e9f10123456789abcdef HTTP/1.1Authorization: Bearer {access_token}
Response (200 OK)
Section titled “Response (200 OK)”{ "acknowledged": true}
Message Lifetime
Section titled “Message Lifetime”Server receives a message ───────► Message is Queued ───────► Client retrieves the message
│ │ │ │ │ │ ▼ ▼
Expires after max_retention_days Client processes the message
│ │ │ ▼
Client acknowledges the message
│ │ │ ▼
Message is deleted from the server
Bulk Acknowledge
Section titled “Bulk Acknowledge”Acknowledge multiple messages in a single request.
Endpoint
Section titled “Endpoint”POST /v1/messages/ack
Request
Section titled “Request”POST /v1/messages/ack HTTP/1.1Authorization: Bearer {access_token}Content-Type: application/json
{ "message_ids": [ "message_1_id_hex", "message_2_id_hex", "message_3_id_hex" ]}
Response (200 OK)
Section titled “Response (200 OK)”{ "acknowledged_count": 3, "failed_count": 0}
When to Use Bulk Acknowledgement
Section titled “When to Use Bulk Acknowledgement”-
After successfully processing multiple retrieved messages
-
When syncing a device after extended offline period
-
For efficiency when processing message batches
Important Notes
Section titled “Important Notes”Message Types
Section titled “Message Types”-
Text: Regular chat messages
-
Media: Messages with file attachments (files are referenced, not included in the MLS ciphertext)
-
SystemOperation: Internal protocol messages (MLS commits, welcomes, delivery receipts, typing indicators, etc)
Message IDs
Section titled “Message IDs”Message IDs must be:
-
Unique per sender (prevent duplicate delivery)
-
32 bytes (64 hex characters)
-
Generated using cryptographically secure random (UUIDv4 or similar)
The server uses message IDs only for deduplication and tracking within queues. It makes ne assumptions about their format beyond length.
Server-Side Message Lifecycle
Section titled “Server-Side Message Lifecycle”-
POST /v1/messages
-> Server queues the message -
GET /v1/messages
-> Client retrieves the message -
Message stays in queue until acknowledged
-
DELETE /v1/messages/{id}
-> Server deletes message
Messages remain retrievable multiple times until acknowledged, allowing clients to retry decryption or handle transient errors.
Federation Transparency
Section titled “Federation Transparency”When sending to a federated recipient (bob@other-server.com
), the server:
-
Discovers the recipient’s home server
-
Authenticates via the federation protocol
-
Forwards the encrypted message
-
Returns
"status": "federated"
The sending client experiences no difference between local and federated delivery. The complexity is hidden by the server.