InfoPackage API
InfoPackages enable ephemeral, encrypted contact exchange and group invitations via QR codes or shareable links. All encryption happens client-side and the server only stores encrypted blobs with no knowledge of their contents.
Use cases:
- Contact sharing via QR codes
- Group invite links
Upload InfoPackage
Section titled “Upload InfoPackage”Uploads an encrypted InfoPackage for sharing. The client must encrypt the package before upload.
Endpoint
Section titled “Endpoint”POST /v1/infopackages/uploadRequest
Section titled “Request”POST /v1/infopackages/upload HTTP/1.1Authorization: Bearer {access_token}Content-Type: application/json{ "ciphertext": "base64_of_encrypted_infopackage_content", "package_type": "identity", "ttl_seconds": 86400, "max_uses": 100}Parameters:
-
ciphertext: Base64-encoded encrypted InfoPackage- Encryption: Client encrypts with random 32-byte key (ChaCha20-Poly1305)
- Privacy: Server stores ciphertext only, cannot decrypt
- Key distribution: Decryption key embedded in QR code or URL fragment
-
package_type: Type hint (identityorgroup_invite)- For client UX (allows displaying “Add Contact” vs “Join Group”)
- Not required for security
-
ttl_seconds: Time-to-live in seconds (min: 60, max: 2592000 / 30 days) -
max_uses: Optional download limit (min: 1, max: 1000, null = unlimited)
Response (200 OK)
Section titled “Response (200 OK)”{ "url_segment": "a1b2c3d4-e5f6-4978-b695-04132e1f2d3c", "expires_at": 1759086400, "max_uses": 100}Response fields:
-
url_segment: Random identifier for fetching (UUIDv4)- Client constructs full URL:
https://{server}/v1/infopackages/{url_segment}
- Client constructs full URL:
-
expires_at: Unix timestamp when package expires -
max_uses: Maximum number of downloads allowed (null if unlimited)
Client responsibilities:
- Construct full URL from server domain + url_segment
- Keep decryption key locally (never send to server)
- Embed key in QR code data or URL fragment
Error Responses
Section titled “Error Responses”400 Bad Request
Section titled “400 Bad Request”{ "error": "INVALID_CONFIG", "message": "TTL must be between 60 seconds and 30 days", "code": 4013}429 Too Many Requests
Section titled “429 Too Many Requests”{ "error": "INFOPACKAGE_RATE_LIMIT", "message": "InfoPackage upload limit exceeded. Maximum 10 per hour, 50 per day.", "code": 4009, "retry_after": 1800}Download InfoPackage
Section titled “Download InfoPackage”Retrieves an encrypted InfoPackage by URL segment. No authentication required, anyone with the URL can fetch it.
Endpoint
Section titled “Endpoint”GET /v1/infopackages/{url_segment}Request
Section titled “Request”GET /v1/infopackages/55b15587-7200-4eae-89ca-be368e5d514e HTTP/1.1Response (200 OK)
Section titled “Response (200 OK)”{ "ciphertext": "base64_encrypted_content", "package_type": "identity", "created_at": 1759000000, "expires_at": 1759086400, "remaining_uses": 99}Response fields:
ciphertext: Base64-encoded encrypted package content (server cannot decrypt)package_type: Type hint (identityorgroup_invite)created_at: Unix timestamp when package was createdexpires_at: Unix timestamp when package expiresremaining_uses: Downloads remaining (null if unlimited)
Client processing:
- Fetch encrypted package from URL
- Extract decryption key from QR code or URL fragment
- Decrypt ciphertext locally
- Parse JSON based on
package_type - Display confirmation prompt
- Perform action if user confirms
Error Responses
Section titled “Error Responses”404 Not Found
Section titled “404 Not Found”{ "error": "NOT_FOUND", "message": "InfoPackage not found or expired"}Causes:
- URL segment doesn’t exist
- Package already expired (TTL passed)
- Package was manually revoked
410 Gone
Section titled “410 Gone”{ "error": "INFOPACKAGE_EXHAUSTED", "message": "InfoPackage has reached maximum uses"}Revoke InfoPackage
Section titled “Revoke InfoPackage”Manually revokes an InfoPackage before expiration. Useful for revoking shared links.
Endpoint
Section titled “Endpoint”DELETE /v1/infopackages/{url_segment}Request
Section titled “Request”DELETE /v1/infopackages/d3c7e5ed-3fbd-405c-9d1d-5b05499d7e04 HTTP/1.1Authorization: Bearer {access_token}Authentication required: Must be the original uploader.
Response (200 OK)
Section titled “Response (200 OK)”{ "revoked": true, "revoked_at": 1759000500}Error Responses
Section titled “Error Responses”403 Forbidden
Section titled “403 Forbidden”{ "error": "UNAUTHORIZED", "message": "You can only revoke InfoPackages you created"}404 Not Found
Section titled “404 Not Found”{ "error": "NOT_FOUND", "message": "InfoPackage not found or already expired"}InfoPackage Structure
Section titled “InfoPackage Structure”These structures are encrypted client-side before upload. The server never sees this data in plaintext.
IdentityInfoPackage
Section titled “IdentityInfoPackage”Used for contact exchange via QR codes.
struct IdentityInfoPackage { user_id: UserId, // Blake3 hash of user public key user_public_key: Ed25519PublicKey, // Ed25519 public key default_persona: Persona, // Default display persona personas: HashMap<PersonaId, Persona>, // Additional personas devices: Vec<DeviceInfo>, // User's devices created_at: u64, // Unix timestamp}See Cryptid’s Identity System for more information about the Persona and DeviceInfo structs.
GroupInviteInfoPackage
Section titled “GroupInviteInfoPackage”Used for group invitations via shareable links.
struct GroupInviteInfoPackage { group_id: GroupId, // UUIDv4 group_name: Option<String>, group_picture: Option<GroupPicture>, group_description: Option<String>, welcome_message: Vec<u8>, // MLS Welcome message invited_by: UserId, // Inviter's user ID invited_by_name: String, // Inviter's display name created_at: u64, // Unix timestamp}Important Notes
Section titled “Important Notes”Privacy Properties
Section titled “Privacy Properties”What servers know:
- URL segment was created (random UUIDv4, no semantic meaning)
- When package expires
- Number of times fetched (if
max_usestracking enabled) - Type hint (
identityorgroup_invite)
What servers DON’T know:
- Identity data inside package (encrypted)
- Group details inside package (encrypted)
- Who fetched the package (downloads are anonymous)
- Who is adding whom as contact
- Who is joining which groups
- Any plaintext content whatsoever
Security Considerations
Section titled “Security Considerations”Encryption:
- Algorithm: ChaCha20-Poly1305
- Key: Random 32 bytes, never sent to server
- Key distribution: Embedded in QR code or URL fragment only
- Server stores ciphertext only (cannot decrypt)
Expiration:
- Automatic cleanup via TTL
- Manual revocation via DELETE endpoint
- Usage limit enforcement (if
max_usesspecified)
Denial of Service:
- Rate limited: 10 uploads/hour, 50 uploads/day per user
- TTL limits: 60 seconds minimum, 30 days maximum
- Max uses limits: 1-1000 downloads per package
URL Format with Embedded Key
Section titled “URL Format with Embedded Key”For shareable links, embed the decryption key in the URL fragment:
https://server.com/invite/a1b2c3d4-e5f6-4978-b695-04132e1f2d3c#32_byte_key_hex ^ key in fragmentWhy use fragment (#)?
- Everything after
#is NOT sent to server in HTTP requests - Browser/app extracts key client-side only
- Server only sees:
/invite/a1b2c3d4-e5f6-4978-b695-04132e1f2d3c - Network observers cannot see the key
QR Code Format
Section titled “QR Code Format”QR codes contain a JSON structure with URL and decryption key:
{ "infopackage_url": "https://server.com/invite/abc123", "infopackage_key": "64_char_hex_encoded_32_bytes", "package_type": "identity", "display_name": "Alice"}Display flow:
- Scanner sees “Alice” immediately (from
display_name) - User confirms: “Add Alice as contact?”
- App fetches encrypted package and decrypts with key
- Adds contact with full identity details
Best Practices
Section titled “Best Practices”For contact sharing:
- Use 24-48 hour TTL (ephemeral)
- Allow multiple uses (
max_uses: 100) - Revoke old QR codes when generating new ones
- Generate new QR codes regularly for privacy
For group invites:
- Use 7-day TTL (enough time to share)
- Limit uses (
max_uses: 10-50) - Revoke after group is full or event ends
- One invite per event/purpose
For security:
- Always use fresh encryption keys (never reuse)
- Securely delete keys after package expires
- Don’t log or persist keys server-side
- Validate ciphertext size (prevent DoS)