# ATP Keyserver API Reference Complete reference for all keyserver API endpoints. ## Authentication Private endpoints require ATProto service auth token in Authorization header: ```http Authorization: Bearer {service_auth_token} ``` Obtain service auth tokens from user's PDS via `com.atproto.server.getServiceAuth`. See [Encryption Protocol](./ENCRYPTION_PROTOCOL.md#authentication-flow) for details. ## Common Headers ### Request Headers ```http Authorization: Bearer {token} # Required for protected endpoints Content-Type: application/json # Required for POST requests User-Agent: {client_info} # Recommended for logging ``` ### Response Headers ```http Content-Type: application/json ``` ## Common Error Responses All endpoints may return these errors: ### 400 Bad Request Invalid request parameters or malformed input. ```json { "error": "Bad Request", "message": "Invalid DID format" } ``` ### 401 Unauthorized Missing or invalid authentication token. ```json { "error": "Unauthorized", "message": "Invalid or expired service auth token" } ``` ### 403 Forbidden Authenticated but not authorized for requested resource. ```json { "error": "Forbidden", "message": "Not a member of this group" } ``` ### 404 Not Found Requested resource does not exist. ```json { "error": "Not Found", "message": "Group not found" } ``` ### 500 Internal Server Error Unexpected server error. ```json { "error": "Internal Server Error", "message": "Database operation failed" } ``` ## Public Endpoints ### Get Service Metadata Returns basic service information. ```http GET / ``` **Authentication:** None **Response:** 200 OK ```json { "name": "@atpkeyserver/server", "version": "0.0.1" } ``` --- ### Get Service DID Document Returns the service's DID document for ATProto service discovery. ```http GET /.well-known/did.json ``` **Authentication:** None **Response:** 200 OK ```json { "@context": ["https://www.w3.org/ns/did/v1"], "id": "did:web:keyserver.example.com", "service": [ { "id": "#atp_keyserver", "type": "AtpKeyserver", "serviceEndpoint": "https://keyserver.example.com/" } ] } ``` --- ### Get Public Key Retrieve any user's public key without authentication. Used for encrypting messages to specific users. ```http GET /xrpc/dev.atpkeyserver.alpha.keypair.getPublicKey ``` **Authentication:** None **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `did` | string | Yes | DID of user (must be `did:web:*` or `did:plc:*`) | | `version` | integer | No | Specific key version to retrieve (default: active version) | **Response:** 200 OK ```json { "publicKey": "a1b2c3d4e5f6...", "version": 1 } ``` **Errors:** - `400` - Invalid DID format or unsupported DID method - `404` - User not found or key version doesn't exist **Example:** ```bash curl "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.keypair.getPublicKey?did=did%3Aplc%3Aabc123" ``` --- ## Personal Key Management ### Get Personal Keypair Retrieve the authenticated user's complete Ed25519 keypair. ```http GET /xrpc/dev.atpkeyserver.alpha.keypair.getKeypair ``` **Authentication:** Required **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `version` | integer | No | Specific key version to retrieve (default: active version) | **Response:** 200 OK ```json { "publicKey": "a1b2c3d4e5f6...", "privateKey": "9f8e7d6c5b4a...", "version": 2 } ``` **Errors:** - `401` - Invalid or expired auth token - `404` - Specified version not found **Example:** ```bash curl -H "Authorization: Bearer ${TOKEN}" \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.keypair.getKeypair" ``` --- ### Rotate Personal Keypair Generate a new keypair version, marking the current version as revoked. ```http POST /xrpc/dev.atpkeyserver.alpha.keypair.rotate ``` **Authentication:** Required **Request Body:** ```json { "reason": "suspected_compromise" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `reason` | string | No | Rotation reason: `suspected_compromise`, `routine_rotation`, `user_requested` (default) | **Response:** 200 OK ```json { "oldVersion": 1, "newVersion": 2, "rotatedAt": "2025-01-24T10:30:00.000Z" } ``` **Errors:** - `401` - Invalid or expired auth token - `500` - Key generation or database error **Example:** ```bash curl -X POST \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"reason":"routine_rotation"}' \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.keypair.rotate" ``` --- ### List Personal Key Versions List all versions of the user's keypair with status and timestamps. ```http GET /xrpc/dev.atpkeyserver.alpha.keypair.listVersions ``` **Authentication:** Required **Response:** 200 OK ```json { "versions": [ { "version": 2, "status": "active", "created_at": "2025-01-24T10:30:00.000Z", "revoked_at": null }, { "version": 1, "status": "revoked", "created_at": "2025-01-20T08:00:00.000Z", "revoked_at": "2025-01-24T10:30:00.000Z" } ] } ``` **Field Definitions:** - `status`: `active` (current version) or `revoked` (no longer active, compromised or rotated out) - `created_at`: ISO 8601 timestamp - `revoked_at`: ISO 8601 timestamp (null if still active) **Errors:** - `401` - Invalid or expired auth token **Example:** ```bash curl -H "Authorization: Bearer ${TOKEN}" \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.keypair.listVersions" ``` --- ### Get Key Access Logs Retrieve access logs for the authenticated user's keys. ```http GET /xrpc/dev.atpkeyserver.alpha.accessLogs.getLogs ``` **Authentication:** Required **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `limit` | integer | No | Maximum number of log entries to return (default: 50) | **Response:** 200 OK ```json { "logs": [ { "version": 2, "accessed_at": "2025-01-24T10:30:00.000Z", "ip": "192.0.2.1", "user_agent": "Mozilla/5.0..." }, { "version": 1, "accessed_at": "2025-01-22T08:15:00.000Z", "ip": "192.0.2.1", "user_agent": "Mozilla/5.0..." } ] } ``` **Field Definitions:** - `version`: Key version that was accessed - `accessed_at`: ISO 8601 timestamp - `ip`: IP address of requester (null if not available) - `user_agent`: User agent string (null if not available) **Errors:** - `400` - Invalid limit parameter - `401` - Invalid or expired auth token **Example:** ```bash curl -H "Authorization: Bearer ${TOKEN}" \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.accessLogs.getLogs?limit=100" ``` --- ## Account Management ### Delete Account **⚠️ WARNING: This action is irreversible and implements cryptographic erasure of ALL your data.** Delete all encryption keys associated with the authenticated user's account. **What the Keyserver Deletes:** - All asymmetric keypair versions - All symmetric group keys owned by the user - All group memberships - All access logs **What the Keyserver Does NOT Delete:** - Encrypted posts from your PDS - Encrypted content from relays - Cached content on other users' devices **Client Responsibility:** Clients SHOULD delete encrypted posts from the user's PDS before calling this endpoint: ```typescript // 1. Delete encrypted posts from PDS (optional but recommended) for (const post of userEncryptedPosts) { await agent.com.atproto.repo.deleteRecord({ repo: userDid, collection: 'app.bsky.feed.post', rkey: post.rkey }) } // 2. Delete keys from keyserver (required for GDPR) await fetch('/xrpc/dev.atpkeyserver.alpha.account.delete', { ... }) ``` **Consequence:** All content encrypted with these keys becomes *permanently unreadable* (cryptographic erasure), even if the encrypted ciphertext persists somewhere on the network. This satisfies GDPR right to erasure while preserving network integrity. ```http POST /xrpc/dev.atpkeyserver.alpha.account.delete ``` **Authentication:** Required **Request Body:** ```json { "confirmation": "DELETE_ALL_MY_DATA" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `confirmation` | string | Yes | Must be exactly `"DELETE_ALL_MY_DATA"` to confirm deletion | **Response:** 200 OK ```json { "keys": 5, "groups": 2, "memberships": 8, "accessLogs": 1234 } ``` **Field Definitions:** - `keys`: Number of key versions deleted (all versions across all time) - `groups`: Number of groups deleted (owned by user, affects all members) - `memberships`: Number of group memberships removed (groups owned by others) - `accessLogs`: Number of access log entries deleted **Errors:** - `400` - Invalid confirmation string (must be exactly `"DELETE_ALL_MY_DATA"`) - `401` - Invalid or expired auth token - `500` - Server error during deletion **Important Warnings:** 1. **Keys deleted, not content**: This endpoint deletes keys only. Clients should delete encrypted posts from PDS separately. 2. **Encrypted content becomes unreadable**: All posts/messages encrypted with deleted keys become permanently unreadable 3. **Affects other users**: Groups you own will be deleted, affecting all members 4. **Content may persist**: Encrypted ciphertext may remain on relays/caches but becomes useless random bytes 5. **No recovery**: This action cannot be undone **GDPR Compliance:** This implements **cryptographic erasure**, which is recognized by EU data protection authorities as effective deletion. The keyserver deletes encryption keys, making encrypted content computationally infeasible to decrypt. Even if encrypted ciphertext persists on the network (e.g., relays don't honor deletion notices), it is no longer "personal data" under GDPR without the decryption keys. **Example:** ```bash curl -X POST \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"confirmation": "DELETE_ALL_MY_DATA"}' \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.account.delete" ``` **Response:** ```json { "keys": 3, "groups": 1, "memberships": 5, "accessLogs": 847 } ``` --- ## Group Key Management ### Get Group Key Retrieve a group's symmetric key for encryption/decryption. ```http GET /xrpc/dev.atpkeyserver.alpha.group.getKey ``` **Authentication:** Required **Authorization:** User must be group owner or member **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `group_id` | string | Yes | Group identifier in format `{owner_did}#{group_name}` | | `version` | integer | No | Specific key version (default: active version) | **Response:** 200 OK ```json { "groupId": "did:plc:abc123#followers", "secretKey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "version": 2 } ``` **Field Definitions:** - `secretKey`: Hex-encoded 32-byte (256-bit) XChaCha20-Poly1305 key - `version`: Key version number **Errors:** - `400` - Missing or invalid group_id parameter - `401` - Invalid or expired auth token - `403` - User not a member of group - `404` - Group or version not found **Example:** ```bash curl -H "Authorization: Bearer ${TOKEN}" \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.group.getKey?group_id=did%3Aplc%3Aabc123%23followers" ``` --- ### Rotate Group Key Generate a new group key version, marking the current version as revoked. Only group owner can perform this action. ```http POST /xrpc/dev.atpkeyserver.alpha.group.rotateKey ``` **Authentication:** Required **Authorization:** User must be group owner **Request Body:** ```json { "group_id": "did:plc:abc123#followers", "reason": "suspected_compromise" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `group_id` | string | Yes | Group identifier | | `reason` | string | No | Rotation reason (default: `user_requested`) | **Response:** 200 OK ```json { "groupId": "did:plc:abc123#followers", "oldVersion": 1, "newVersion": 2, "rotatedAt": "2025-01-24T10:30:00.000Z" } ``` **Errors:** - `400` - Missing or invalid group_id - `401` - Invalid or expired auth token - `403` - User not group owner - `404` - Group not found **Example:** ```bash curl -X POST \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"group_id":"did:plc:abc123#followers","reason":"routine_rotation"}' \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.group.rotateKey" ``` --- ### List Group Key Versions List all versions of a group key with status and timestamps. Owner and members can view. ```http GET /xrpc/dev.atpkeyserver.alpha.group.listVersions ``` **Authentication:** Required **Authorization:** User must be group owner or member **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `group_id` | string | Yes | Group identifier | **Response:** 200 OK ```json { "groupId": "did:plc:abc123#followers", "versions": [ { "version": 2, "status": "active", "created_at": "2025-01-24T10:30:00.000Z", "revoked_at": null }, { "version": 1, "status": "revoked", "created_at": "2025-01-20T08:00:00.000Z", "revoked_at": "2025-01-24T10:30:00.000Z" } ] } ``` **Errors:** - `400` - Missing or invalid group_id - `401` - Invalid or expired auth token - `403` - User not a member of group - `404` - Group not found **Example:** ```bash curl -H "Authorization: Bearer ${TOKEN}" \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.group.listVersions?group_id=did%3Aplc%3Aabc123%23followers" ``` --- ## Group Membership Management ### Add Group Member Add a user to a group, granting them access to the group key. Only group owner can perform this action. ```http POST /xrpc/dev.atpkeyserver.alpha.group.addMember ``` **Authentication:** Required **Authorization:** User must be group owner **Request Body:** ```json { "group_id": "did:plc:abc123#followers", "member_did": "did:plc:xyz789" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `group_id` | string | Yes | Group identifier | | `member_did` | string | Yes | DID of user to add | **Response:** 200 OK ```json { "groupId": "did:plc:abc123#followers", "memberDid": "did:plc:xyz789", "status": "added" } ``` **Errors:** - `400` - Missing or invalid parameters - `401` - Invalid or expired auth token - `403` - User not group owner - `404` - Group not found - `409` - Member already in group **Example:** ```bash curl -X POST \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"group_id":"did:plc:abc123#followers","member_did":"did:plc:xyz789"}' \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.group.addMember" ``` --- ### Remove Group Member Remove a user from a group, revoking their access to the group key. Only group owner can perform this action. ```http POST /xrpc/dev.atpkeyserver.alpha.group.removeMember ``` **Authentication:** Required **Authorization:** User must be group owner **Request Body:** ```json { "group_id": "did:plc:abc123#followers", "member_did": "did:plc:xyz789" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `group_id` | string | Yes | Group identifier | | `member_did` | string | Yes | DID of user to remove | **Response:** 200 OK ```json { "groupId": "did:plc:abc123#followers", "memberDid": "did:plc:xyz789", "status": "removed" } ``` **Errors:** - `400` - Missing or invalid parameters - `401` - Invalid or expired auth token - `403` - User not group owner - `404` - Group not found or member not in group **Example:** ```bash curl -X POST \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"group_id":"did:plc:abc123#followers","member_did":"did:plc:xyz789"}' \ "https://keyserver.example.com/xrpc/dev.atpkeyserver.alpha.group.removeMember" ``` --- ## Rate Limits Rate limits are not currently implemented in this version, but in future versions a rate limit system may look like this: - Public endpoints: 100 requests/minute per IP - Authenticated endpoints: 1000 requests/minute per DID - Key rotation: 10 requests/hour per DID - Member management: 100 requests/hour per DID The response headers this system will use are the following: ```http X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 950 X-RateLimit-Reset: 1706097600 ``` ## Pagination Currently, pagination is not implemented for list endpoints. All results are returned in a single response. Future versions may add cursor-based pagination for: - `/xrpc/dev.atpkeyserver.alpha.accessLogs.getLogs` - `/xrpc/dev.atpkeyserver.alpha.keypair.listVersions` - `/xrpc/dev.atpkeyserver.alpha.group.listVersions` ## Versioning **Protocol Version:** `dev.atpkeyserver.alpha` This is a development version of the API. Breaking changes may occur before version 1.0 is released. **API Changelog:** - 2025-01-24: Added account deletion endpoint (GDPR compliance) - 2025-01-24: Added access log endpoint with 90-day retention - 2025-01-23: Added key versioning support - 2025-01-22: Added group member management - 2025-01-20: Initial release ## SDKs and Libraries **Official:** - [@atpkeyserver/client](https://www.npmjs.com/package/@atpkeyserver/client) - TypeScript/JavaScript client (not released yet) **Community:** - (Submit your implementation via PR to be listed here) ## Support For API questions or issues: - Issues: [atp-keyserver/issues](https://codeberg.org/juandjara/atp-keyserver/issues) - Documentation: [docs/](https://codeberg.org/juandjara/atp-keyserver/src/branch/main/docs) For a guide on security vulnerabilities, see [SECURITY.md](./SECURITY.md#security-contact).