Federation API
Federation enables Cryptid servers to route messages across different domains, creating a truly decentralized messaging network. Servers authenticate with each other using server certificates, then relay encrypted messages without ever understanding their content or relationships.
Key Principle: Federated servers act as encrypted message relays. They forward opaque MLS ciphertext between domains without knowing senders, content, or social graphs.
Cross-Server Message Delivery
Section titled “Cross-Server Message Delivery”Delivers messages from another Cryptid server to local recipients. The receiving server validates the federation token, checks that recipients are announced locally, and queues messages for delivery.
Federation Flow
Section titled “Federation Flow”Device A Server A Server B Device B
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 1. Announce (Register) │ │ │ ├───────────────────────────►│ │ │ │ │ │ │ │ │ │ │ │ JWT Token │ │ │ │◄───────────────────────────┤ │ │ │ │ │ │ │ │ │ │ │ 2. Send encrypted msg │ │ │ ├───────────────────────────►│ │ │ │ │ │ │ │ │ 3. Federation │ │ │ ├─────────────────────────────────►│ │ │ │ (relay) │ │ │ │ │ 4. Queue msg │ │ │ ├─────────────────────────────►│ │ │ │ │ │ │ │ │ │ │ │ 5. Retrieve (WebSocket) │ │ │ │◄─────────────────────────────┤ │ │ │ │Neither Server A nor Server B learns message content or sender identity.
Transaction IDs
Section titled “Transaction IDs”Transaction IDs are UUIDv4 identifiers that ensure idempotency for federation requests. If a request is retried with the same transaction ID, the server returns the cached response without re-processing messages.
Format: UUIDv4 (e.g., a1b2c3d4-e5f6-4978-b695-c1d2e3f4a5b6)
Purpose:
- Prevents duplicate message delivery
- Enables safe retries of failed federation requests
- Server caches transaction IDs for ~1 hour
Endpoint
Section titled “Endpoint”PUT /federation/v1/deliver/{transaction_id}Request
Section titled “Request”PUT /federation/v1/deliver/b523a672-1e93-4cd4-ad11-ade909321ef6 HTTP/1.1Content-Type: application/jsonAuthorization: Bearer federation_tokenX-Origin-Server: alice.server1.com{ "origin_server": "alice.server1.com", "transaction_id": "b523a672-1e93-4cd4-ad11-ade909321ef6", "timestamp": 1759000000, "messages": [ { "message_id": "019a821b-d8d4-7dc1-8ea4-28dfcf55346b", "recipient_address": "0e8636c4-8cba-4064-bb74-7c13fee290a0@server2.com", // Bob's delivery address "mls_ciphertext": "base64_encrypted_content", "sender_signature": "ed25519_signature", "timestamp": 1758999498, } ]}Parameters:
-
origin_server: Domain of the originating server (must matchX-Origin-Serverheader) -
transaction_id: UUIDv4 identifier for idempotent delivery (must match URL path) -
timestamp: Unix timestamp of the federation request (not message timestamp) -
messages[]: Array of encrypted messages to deliver
Message Fields:
-
message_id: UUIDv7 identifier (time-ordered message ID) -
recipient_address: Delivery address (UUIDv4 prefix + domain) announced on receiving server -
mls_ciphertext: Encrypted payload (opaque to both servers) -
sender_signature: Ed25519 signature (server does not verify, recipient does) -
timestamp: Unix timestamp when message was sent
The sender’s address is intentionally NOT included in the federated messages to protect sender privacy. Recipients can derive sender identity from the MLS group context or signature validation.
Response (200 OK)
Section titled “Response (200 OK)”{ "transaction_id": "b523a672-1e93-4cd4-ad11-ade909321ef6", "status": "accepted", "accepted_messages": 1, "rejected_messages": 0, "timestamp": 1759000000}Response Fields:
-
status:accepted: All messages queued successfullypartial: Some messages rejected (seerejected_messages)rejected: All messages rejected
-
accepted_messages: Count of messages successfully queued -
rejected_messages: Count of messages rejected (recipient not announced, invalid format, etc) -
timestamp: Server’s current timestamp
Partial Success: If some recipients are not announced locally, those messages are rejected while others are accepted.
Error Messages
Section titled “Error Messages”Server error notifications:
{ "type": "error", "error": "INVALID_TOKEN", "message": "Federation token invalid or expired", "code": 4008}Common Errors:
4001: Invalid signature/certificate4004: Rate limit exceeded4006: Federation failed4008: Token expired
See Error Handling for complete error code reference.
Federation Authentication
Section titled “Federation Authentication”Authenticates a server for cross-server message delivery. Servers must prove ownership of their domain before they can relay messages, preventing impersonation attacks.
Certificate Validation: The receiving server validates that the origin server controls the claimed domain using the provided server certificate.
Endpoint
Section titled “Endpoint”POST /federation/v1/authRequest
Section titled “Request”POST /federation/v1/auth HTTP/1.1Content-Type: application/json{ "origin_server": "alice.server1.com", "server_certificate": "base64_server_certificate", "challenge_response": "proof_of_server_ownership"}Parameters:
-
origin_server: Domain of the server requesting federation access -
server_certificate: Base64 encoded server certificate (TLS certificate or equivalent) -
challenge_response: Cryptographic proof of domain ownership
Challenge-Response Flow:
-
Origin server requests authentication with certificate
-
Receiving server validates certificate matches
origin_serverdomain -
Receiving server generates random challenge:
nonce = random_bytes(32) -
Origin server signs challenge with server private key:
signature = sign(server_key, nonce) -
Receiving server verifies signature with certificate public key
-
Receiving server issues time-limited federation token
Response (200 OK)
Section titled “Response (200 OK)”{ "federation_token": "jwt_federation_token", "expires_at": 1759003600, "rate_limits": { "messages_per_minute": 1000, "transactions_per_minute": 100 }}Response Fields:
-
federation_token: JWT token for authenticated federation requests -
expires_at: Unix timestamp when token expires (typically 1 hour) -
rate_limits: Server’s federation rate limits-
messages_per_minute: Total individual messages allowed per minute -
transactions_per_minute: Total federation requests allowed per minute
-
Token Usage: Include this token in the Authorization: Bearer {federation_token} header for all /federation/v1/deliver requests.
Rate Limits Explained:
-
If you send 10 messages in 1 transaction, you consume: 1 transaction, 10 messages
-
If you send 1 message in 10 transactions, you consume: 10 transactions, 10 messages
-
Best Practice: Batch messages efficiently (e.g., 10 messages/transaction)
Error Responses
Section titled “Error Responses”403 Forbidden
Section titled “403 Forbidden”{ "error": "INVALID_CERTIFICATE", "message": "Server certificate invalid or domain mismatch", "code": 4001}503 Service Unavailable
Section titled “503 Service Unavailable”{ "error": "FEDERATION_DISABLED", "message": "This server does not support federation", "code": 5003}Federation Best Practices
Section titled “Federation Best Practices”Batching Messages
Section titled “Batching Messages”Efficient:
{ "transaction_id": "7d8a1fe4-58e4-4153-b579-dcbd119afcb9", "messages": [ // 10 messages for different recipients ]}// Uses: 1 transaction, 10 messagesInefficient:
{ // 10 separate API calls, each with 1 message // Uses: 10 transactions, 10 messages // Hits transaction limit 10x faster}Recommendation: Batch up to 50-100 messages per transaction for optimal efficiency.
Token Caching
Section titled “Token Caching”Federation tokens typically expire after 1 hour. Cache them and reuse.
struct FederationClient { tokens: HashMap<String, CachedToken>,}
struct CachedToken { token: String, expires_at: u64,}
impl FederationClient { async fn get_token(&mut self, target_domain: &str) -> Result<String> { if let Some(cached) = self.tokens.get(target_domain) { let now = current_timestamp(); // Refresh if expiring in < 5 minutes if cached.expires_at > now && (cached.expires_at - now) > 300 { return Ok(cached.token.clone()) } }
// Authenticate and cache new token let response = self.authenticate(target_domain).await?; self.tokens.insert(target_domain.to_string(), CachedToken { token: response.federation_token.clone(), expires_at: response.expires_at, });
Ok(response.federation_token) }}Server Discovery
Section titled “Server Discovery”Before federating with a server, discover its capabilities:
async fn discover_server(domain: &str) -> Result<ServerCapabilities> { let url = format!("https://{domain}/.well-known/cryptid"); let response = reqwest::get(&url).await?; let caps: ServerCapabilities = response.json().await?;
if !caps.federation_enabled { return Err(anyhow!("Federation not supported")); }
Ok(caps)}See Server Discovery for details on the .well-known/cryptid endpoint.
Retry Strategy
Section titled “Retry Strategy”Federation requests can fail due to network issues or temporary server unavailability:
async fn deliver_with_retry( target_domain: &str, messages: Vec<Message>, max_retries: u32,) -> Result<FederationResponse> { let mut retry_count = 0;
loop { match deliver_messages(target_domain, &messages).await { Ok(response) => return Ok(response), Err(e) if retry_count < max_retries => { retry_count += 1;
// Exponential backoff: 1s, 2s, 4s, 8s, ... let delay = 2u64.pow(retry_count - 1); tokio::time::sleep(Duration::from_secs(delay)).await; } Err(e) => return Err(e), } }}Important Notes
Section titled “Important Notes”Transaction IDs
Section titled “Transaction IDs”Transaction IDs prevent duplicate message delivery if federation requests are retried:
-
Format: UUIDv4 (e.g.,
a1b2c3d4-e5f6-4978-b695-c1d2e3f4a5b6) -
Must be unique per federation request (not per message)
-
Must match between URL path and request body
-
Receiving server caches transaction IDs for ~1 hour to detect duplicates
-
Retrying the same transaction ID returns the cached response (idempotent)
Message Privacy
Section titled “Message Privacy”Federation maintains Cryptid’s privacy principles:
Servers relay but never learn:
-
Message content (MLS encrypted end-to-end)
-
Sender identity (not included in federation request)
-
Social relationships (no group membership lists)
-
User metadata (no profiles, no presence information)
What servers DO know:
-
Recipient device address (required for delivery routing)
-
Origin server domain (e.g., alice.example.com, but not the specific device)
-
Which domains exchange messages (network metadata)
-
Message delivery timestamps (for routing only)
-
Approximate message volume between servers
This metadata is minimal and inherent to any federated system.
Message Type Handling
Section titled “Message Type Handling”All messages are treated identically during federation - servers cannot distinguish between application messages and protocol operations.
Message types are determined client-side during MLS decryption:
CryptidMessage (application messages):
- Text, reactions, edits, file attachments
- Persona updates, read receipts, typing indicators
SystemOperation (protocol operations):
- MLS commits and welcomes
- Group extension updates (permissions, metadata)
- Address rotation notifications
Both types are encrypted end-to-end and appear as identical opaque ciphertext to federated servers. See CryptidMessage Reference for complete message format specification.
Security Considerations
Section titled “Security Considerations”Certificate Validation
Section titled “Certificate Validation”Production implementations must:
-
Verify server certificate is signed by trusted CA
-
Check certificate matches claimed domain (DNS validation)
-
Validate certificate has not expired or been revoked
-
Use TLS 1.3+ for all federation connections
Denial of Service Protection
Section titled “Denial of Service Protection”-
Rate limiting prevents spam from malicious servers
-
Transaction ID deduplication prevents replay attacks
-
Token expiration limits impact of stolen credentials
Trust Model
Section titled “Trust Model”Servers trust each other to:
- Route messages honestly (no guaranteed delivery)
- Follow protocol rules (Byzantine fault tolerance not guaranteed)
- Respect rate limits and federation policies
Cryptographic guarantees prevent:
- Message injection (Ed25519 signatures required)
- Content tampering (MLS encryption with integrity)
- Identity spoofing (device ID verification via public keys)
No trust required for:
- Message authenticity (cryptographically enforced)
- Content privacy (end-to-end encrypted)
- Sender verification (clients validate signatures)