{"openapi":"3.1.0","info":{"title":"sealed API","version":"1.0.0","description":"Public REST API for sealed — the wallet-native, end-to-end encrypted messenger. The API resolves public directory data and routes already-sealed envelopes; it never sees message plaintext. Authenticated endpoints are rate-limited to 120 requests/minute per key."},"servers":[{"url":"https://api.sealedlabs.net"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key (sk_live_…) or group bot token (gtk_live_…) for group routes."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"Envelope":{"type":"object","description":"An opaque sealed message. All content fields are ciphertext produced client-side.","properties":{"id":{"type":"string","format":"uuid"},"senderAddress":{"type":"string"},"recipientAddress":{"type":"string"},"recipientDeviceKey":{"type":"string"},"ciphertext":{"type":"string"},"ephemeralPublicKey":{"type":"string"},"nonce":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"delivered":{"type":"boolean"}}},"EnvelopeInput":{"type":"object","description":"All content fields must be genuinely sealed: ephemeralPublicKey is 64 hex chars (32-byte X25519), nonce is base64 of a 12-byte GCM IV, ciphertext is base64 of AES-GCM output. Base64-wrapped plaintext is rejected (400).","required":["toAddress","ciphertext","ephemeralPublicKey","nonce"],"properties":{"toAddress":{"type":"string","description":"0x recipient address"},"recipientDeviceKey":{"type":"string","description":"Device public key the ciphertext was sealed for (from GET /api/v1/keys/{address})"},"ciphertext":{"type":"string"},"ephemeralPublicKey":{"type":"string"},"nonce":{"type":"string"},"asRequest":{"type":"boolean","description":"Deliver as a contact request intro"}}},"GroupEnvelopeInput":{"type":"object","required":["toAddress","ciphertext","ephemeralPublicKey","nonce"],"properties":{"toAddress":{"type":"string","description":"Must be a current group member"},"recipientDeviceKey":{"type":"string"},"ciphertext":{"type":"string"},"ephemeralPublicKey":{"type":"string"},"nonce":{"type":"string"}}},"SendResult":{"type":"object","properties":{"routed":{"type":"string","enum":["inbox","request","blocked","stake-gate"]},"stakeRequired":{"type":"string","description":"Present when routing requires a refundable anti-spam stake (in ETH/CRO)"},"envelope":{"$ref":"#/components/schemas/Envelope"}},"required":["routed"]},"DirectoryEntry":{"type":"object","properties":{"handle":{"type":"string"},"address":{"type":"string"},"verified":{"type":"boolean"}}}}},"paths":{"/api/v1/me":{"get":{"summary":"Introspect the calling key","description":"Returns the wallet address that owns the key, its handle, verified-business status, registered device count, and purchased entitlements.","responses":{"200":{"description":"Caller identity","content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string"},"handle":{"type":["string","null"]},"verified":{"type":"boolean"},"deviceKeys":{"type":"integer"},"entitlements":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/handles/{handle}":{"get":{"summary":"Resolve a handle","parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"},"description":"Handle without the @ prefix"}],"responses":{"200":{"description":"Handle record"},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Handle not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/directory":{"get":{"summary":"Search the public directory","parameters":[{"name":"q","in":"query","schema":{"type":"string"},"description":"Handle or address fragment. Omit to list recent claims."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}}],"responses":{"200":{"description":"Matching directory entries","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/DirectoryEntry"}}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/keys/{address}":{"get":{"summary":"Fetch device public keys for an address","description":"Current non-revoked device public keys to seal ciphertext against. Fetch fresh keys right before encrypting.","parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$"}}],"responses":{"200":{"description":"Device keys","content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string"},"deviceKeys":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Invalid address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No registered devices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/envelopes":{"get":{"summary":"Pull your inbox","description":"Undelivered envelopes addressed to the key's wallet. Non-consuming — envelopes remain queued until acked.","responses":{"200":{"description":"Queued envelopes","content":{"application/json":{"schema":{"type":"object","properties":{"envelopes":{"type":"array","items":{"$ref":"#/components/schemas/Envelope"}}}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Send sealed envelopes","description":"Submit one envelope (object) or a batch (array, or {envelopes: [...]}). The relay only ever carries ciphertext.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EnvelopeInput"},{"type":"array","items":{"$ref":"#/components/schemas/EnvelopeInput"}}]}}}},"responses":{"200":{"description":"Send result (or {results: [...]} for a batch), including anti-spam routing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}},"400":{"description":"Malformed envelope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/groups":{"get":{"summary":"List groups","description":"Public group directory, or the caller's own groups with ?mine=1. sk_live_… keys only.","parameters":[{"name":"mine","in":"query","schema":{"type":"string","enum":["1"]},"description":"Return the key owner's groups instead"}],"responses":{"200":{"description":"{ groups: [{ id, name, isPublic, verified, hasPassword, avatar, memberCount, createdAt }] }"},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/groups/{id}":{"get":{"summary":"Get group + member device keys","description":"Authorized with gtk_live_… (scoped to this group) or sk_live_… when the key owner is a member. Includes memberDeviceKeys for client-side sealing.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Group view with memberDeviceKeys","content":{"application/json":{"schema":{"type":"object","properties":{"group":{"type":"object"},"auth":{"type":"object","properties":{"via":{"type":"string","enum":["group-token","api-key"]},"senderAddress":{"type":"string"}}}}}}}},"401":{"description":"Invalid or missing API key / group token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/groups/{id}/envelopes":{"post":{"summary":"Send sealed envelopes into a group","description":"Batch of opaque envelopes to current members. With gtk_live_… the relay sender is the group creator.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/GroupEnvelopeInput"},{"type":"array","items":{"$ref":"#/components/schemas/GroupEnvelopeInput"}},{"type":"object","properties":{"envelopes":{"type":"array","items":{"$ref":"#/components/schemas/GroupEnvelopeInput"}}}}]}}}},"responses":{"200":{"description":"{ results: [...], via }","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/SendResult"}},"via":{"type":"string","enum":["group-token","api-key"]}}}}}},"400":{"description":"Envelope incomplete or recipient not a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key / group token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/envelopes/ack":{"post":{"summary":"Acknowledge decrypted envelopes","description":"Confirm envelopes you have decrypted and stored so the relay can prune them.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","items":{"type":"string"},"minItems":1}}}}}},"responses":{"200":{"description":"{ ok: true, acked: n }"},"400":{"description":"ids required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}