ATP Keyserver API Reference#
Complete reference for all keyserver API endpoints.
Authentication#
Private endpoints require ATProto service auth token in Authorization header:
Authorization: Bearer {service_auth_token}
Obtain service auth tokens from user's PDS via com.atproto.server.getServiceAuth. See Encryption Protocol for details.
Common Headers#
Request Headers#
Authorization: Bearer {token} # Required for protected endpoints
Content-Type: application/json # Required for POST requests
User-Agent: {client_info} # Recommended for logging
Response Headers#
Content-Type: application/json
Common Error Responses#
All endpoints may return these errors:
400 Bad Request#
Invalid request parameters or malformed input.
{
"error": "Bad Request",
"message": "Invalid DID format"
}
401 Unauthorized#
Missing or invalid authentication token.
{
"error": "Unauthorized",
"message": "Invalid or expired service auth token"
}
403 Forbidden#
Authenticated but not authorized for requested resource.
{
"error": "Forbidden",
"message": "Not a member of this group"
}
404 Not Found#
Requested resource does not exist.
{
"error": "Not Found",
"message": "Group not found"
}
500 Internal Server Error#
Unexpected server error.
{
"error": "Internal Server Error",
"message": "Database operation failed"
}
Public Endpoints#
Get Service Metadata#
Returns basic service information.
GET /
Authentication: None
Response: 200 OK
{
"name": "@atpkeyserver/server",
"version": "0.0.1"
}
Get Service DID Document#
Returns the service's DID document for ATProto service discovery.
GET /.well-known/did.json
Authentication: None
Response: 200 OK
{
"@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.
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
{
"publicKey": "a1b2c3d4e5f6...",
"version": 1
}
Errors:
400- Invalid DID format or unsupported DID method404- User not found or key version doesn't exist
Example:
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.
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
{
"publicKey": "a1b2c3d4e5f6...",
"privateKey": "9f8e7d6c5b4a...",
"version": 2
}
Errors:
401- Invalid or expired auth token404- Specified version not found
Example:
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.
POST /xrpc/dev.atpkeyserver.alpha.keypair.rotate
Authentication: Required
Request Body:
{
"reason": "suspected_compromise"
}
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | No | Rotation reason: suspected_compromise, routine_rotation, user_requested (default) |
Response: 200 OK
{
"oldVersion": 1,
"newVersion": 2,
"rotatedAt": "2025-01-24T10:30:00.000Z"
}
Errors:
401- Invalid or expired auth token500- Key generation or database error
Example:
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.
GET /xrpc/dev.atpkeyserver.alpha.keypair.listVersions
Authentication: Required
Response: 200 OK
{
"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) orrevoked(no longer active, compromised or rotated out)created_at: ISO 8601 timestamprevoked_at: ISO 8601 timestamp (null if still active)
Errors:
401- Invalid or expired auth token
Example:
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.
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
{
"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 accessedaccessed_at: ISO 8601 timestampip: IP address of requester (null if not available)user_agent: User agent string (null if not available)
Errors:
400- Invalid limit parameter401- Invalid or expired auth token
Example:
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:
// 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.
POST /xrpc/dev.atpkeyserver.alpha.account.delete
Authentication: Required
Request Body:
{
"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
{
"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 token500- Server error during deletion
Important Warnings:
- Keys deleted, not content: This endpoint deletes keys only. Clients should delete encrypted posts from PDS separately.
- Encrypted content becomes unreadable: All posts/messages encrypted with deleted keys become permanently unreadable
- Affects other users: Groups you own will be deleted, affecting all members
- Content may persist: Encrypted ciphertext may remain on relays/caches but becomes useless random bytes
- 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:
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:
{
"keys": 3,
"groups": 1,
"memberships": 5,
"accessLogs": 847
}
Group Key Management#
Get Group Key#
Retrieve a group's symmetric key for encryption/decryption.
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
{
"groupId": "did:plc:abc123#followers",
"secretKey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"version": 2
}
Field Definitions:
secretKey: Hex-encoded 32-byte (256-bit) XChaCha20-Poly1305 keyversion: Key version number
Errors:
400- Missing or invalid group_id parameter401- Invalid or expired auth token403- User not a member of group404- Group or version not found
Example:
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.
POST /xrpc/dev.atpkeyserver.alpha.group.rotateKey
Authentication: Required
Authorization: User must be group owner
Request Body:
{
"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
{
"groupId": "did:plc:abc123#followers",
"oldVersion": 1,
"newVersion": 2,
"rotatedAt": "2025-01-24T10:30:00.000Z"
}
Errors:
400- Missing or invalid group_id401- Invalid or expired auth token403- User not group owner404- Group not found
Example:
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.
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
{
"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_id401- Invalid or expired auth token403- User not a member of group404- Group not found
Example:
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.
POST /xrpc/dev.atpkeyserver.alpha.group.addMember
Authentication: Required
Authorization: User must be group owner
Request Body:
{
"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
{
"groupId": "did:plc:abc123#followers",
"memberDid": "did:plc:xyz789",
"status": "added"
}
Errors:
400- Missing or invalid parameters401- Invalid or expired auth token403- User not group owner404- Group not found409- Member already in group
Example:
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.
POST /xrpc/dev.atpkeyserver.alpha.group.removeMember
Authentication: Required
Authorization: User must be group owner
Request Body:
{
"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
{
"groupId": "did:plc:abc123#followers",
"memberDid": "did:plc:xyz789",
"status": "removed"
}
Errors:
400- Missing or invalid parameters401- Invalid or expired auth token403- User not group owner404- Group not found or member not in group
Example:
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:
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 - 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
- Documentation: docs/
For a guide on security vulnerabilities, see SECURITY.md.