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/messagesRequest
Section titled “Request”POST /v1/messages HTTP/1.1Authorization: Bearer {access_token}Content-Type: application/json{ "message_id": "019a821b-d8d4-7dc1-8ea4-28dfcf55346b", "recipient_address": "98ea6968-309e-47a9-aab8-598b50f124b0@server2.example.com", "mls_ciphertext": "base64_mls_encrypted_payload_here", "sender_signature": "ed25519_signature_over_ciphertext", "timestamp": 1759697179}Parameters:
message_id: UUIDv7 message identifier (incorporates timestamp for ordering)recipient_address: Target delivery address (UUIDv4 prefix + server domain)mls_ciphertext: Base64 encoded MLS encrypted payload (server cannot decrypt)sender_signature: Ed25519 signature over ciphertext
Response (202 Accepted)
Section titled “Response (202 Accepted)”{ "message_id": "019a821b-d8d4-7dc1-8ea4-28dfcf55346b", "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/batchRequest
Section titled “Request”POST /v1/messages/batch HTTP/1.1Authorization: Bearer {access_token}Content-Type: application/json{ "messages": [ { "message_id": "019a821b-d8d4-7dc1-8ea4-28dfcf55346b", "recipient_address": "39801d18-8ded-4355-867f-d801817124df@server1.com", "mls_ciphertext": "base64_ciphertext_1", "sender_signature": "signature_1", "timestamp": 1759697179 }, { "message_id": "019a821b-d8d4-7dc1-8ea4-28e09b9f1af1", "recipient_address": "87a41c12-cb15-4ba5-a76f-ca4faae8d9cf@server2.com", "mls_ciphertext": "base64_ciphertext_2", "sender_signature": "signature_2", "timestamp": 1759697179 } ]}Response (202 Accepted)
Section titled “Response (202 Accepted)”{ "accepted_count": 2, "rejected_count": 0, "message_statuses": [ { "message_id": "019a821b-d8d4-7dc1-8ea4-28dfcf55346b", "status": "queued" }, { "message_id": "019a821b-d8d4-7dc1-8ea4-28e09b9f1af1", "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/messagesQuery 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": "019a821b-d8d4-7dc1-8ea4-28dfcf55346b", "mls_ciphertext": "base64_encrypted_content", "sender_signature": "ed25519_signature", "timestamp": 1758999498, "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
sinceparameter for incremental sync (reduces bandwidth) -
Respect
next_poll_intervalto 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/019a821b-d8d4-7dc1-8ea4-28dfcf55346b 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 serverBulk Acknowledge
Section titled “Bulk Acknowledge”Acknowledge multiple messages in a single request.
Endpoint
Section titled “Endpoint”POST /v1/messages/ackRequest
Section titled “Request”POST /v1/messages/ack HTTP/1.1Authorization: Bearer {access_token}Content-Type: application/json{ "message_ids": [ "019a821b-d8d4-7dc1-8ea4-28dfcf55346b", "019a821b-d8d4-7dc1-8ea4-28e09b9f1af1", "019a821b-d8d4-7dc1-8ea4-28f7a2a06533" ]}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”-
CryptidMessage: Application-level messages (text, reactions, edits, file attachments, persona updates, etc). See CryptidMessage Reference for complete message format specification.
-
SystemOperation: Protocol-level operations (MLS commits, MLS welcomes, group extension updates, address rotation notifications). These are MLS group state changes, not user-visible messages.
Message IDs
Section titled “Message IDs”Message IDs must be:
-
UUIDv7 format (time-ordered for efficient indexing)
-
Unique per sender (prevents duplicate delivery)
-
36 characters with hyphens or 32 hex characters without
-
Incorporates Unix millisecond timestamp for temporal ordering
Example: 019a782a-4cdc-7251-917b-a77386d6eb08
Why UUIDv7?
- Provides natural time-based ordering
- Efficient database indexing
- Prevents collision across distributed devices
- Clients can extract timestamp if needed
The server uses message IDs only for deduplication and tracking within queues. It makes no assumptions about internal structure beyond format compliance.
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 (746c3eff-3182-47bb-aa40-11a81e53f808@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.
File Attachments and Hashing
Section titled “File Attachments and Hashing”When CryptidMessage includes file attachments (via an AttachFile message action), files are referenced using their Blake3 hash for verification:
{ "file_ref": { "size": 2500000, "plaintext_hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", // Blake3 hash of file "file_id": { "uploader": "8f6b753d772275127557397be1edce476cd698ed5f09a2b4a70fb64a0577ab2a", // DeviceId "id": "5c07d23f-7655-4c05-9f96-a1625ea60506" // FileId (Uuidv4) } }}The server routes these references but never accesses file content. See CryptidMessage Reference for complete file transfer protocol.