Skip to content

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

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.

POST /v1/messages
POST /v1/messages HTTP/1.1
Authorization: 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
{
"message_id": "019a821b-d8d4-7dc1-8ea4-28dfcf55346b",
"status": "queued",
"delivery_estimate": "immediate"
}

Response Fields:

  • status:
    • queued: Message stored locally for retrieval by recipient
    • federated: Message forwarded to recipient’s home server
  • delivery_estimate:
    • immediate: Recipient is online or will retrieve the message soon
    • delayed: Recipient may be offline (message expires per retention policy)

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).

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)

POST /v1/messages/batch
POST /v1/messages/batch HTTP/1.1
Authorization: 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
}
]
}
{
"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"
}
]
}

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).

GET /v1/messages

Query Parameters:

  • since (optional): Unix timestamp for incremental sync
  • limit (optional): Max messages to return (default: 100, max: 1000)
GET /v1/messages?since=1759697179&limit=50 HTTP/1.1
Authorization: Bearer {access_token}
{
"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:

  1. Use the since parameter for incremental sync (reduces bandwidth)

  2. Respect next_poll_interval to avoid rate limiting

  3. Consider WebSocket streaming for real-time applications

  4. Acknowledge messages promptly to free server storage

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

DELETE /v1/messages/{message_id}
DELETE /v1/messages/019a821b-d8d4-7dc1-8ea4-28dfcf55346b HTTP/1.1
Authorization: Bearer {access_token}
{
"acknowledged": true
}
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

Acknowledge multiple messages in a single request.

POST /v1/messages/ack
POST /v1/messages/ack HTTP/1.1
Authorization: Bearer {access_token}
Content-Type: application/json
{
"message_ids": [
"019a821b-d8d4-7dc1-8ea4-28dfcf55346b",
"019a821b-d8d4-7dc1-8ea4-28e09b9f1af1",
"019a821b-d8d4-7dc1-8ea4-28f7a2a06533"
]
}
{
"acknowledged_count": 3,
"failed_count": 0
}
  • After successfully processing multiple retrieved messages

  • When syncing a device after extended offline period

  • For efficiency when processing message batches

  • 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 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.

  1. POST /v1/messages -> Server queues the message

  2. GET /v1/messages -> Client retrieves the message

  3. Message stays in queue until acknowledged

  4. DELETE /v1/messages/{id} -> Server deletes message

Messages remain retrievable multiple times until acknowledged, allowing clients to retry decryption or handle transient errors.

When sending to a federated recipient (746c3eff-3182-47bb-aa40-11a81e53f808@other-server.com), the server:

  1. Discovers the recipient’s home server

  2. Authenticates via the federation protocol

  3. Forwards the encrypted message

  4. Returns "status": "federated"

The sending client experiences no difference between local and federated delivery. The complexity is hidden by the server.

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.