Pubky Core API Reference
The Pubky Core protocol defines a RESTful HTTP API for storing and retrieving data on Homeservers. This document describes the complete API specification.
Base URL
Section titled “Base URL”All API endpoints are relative to the Homeserver base URL:
https://homeserver.example.comHomeserver URLs are discovered via PKARR records published to the Mainline DHT.
Authentication
Section titled “Authentication”See Authentication for conceptual overview.
Public Key Authentication
Section titled “Public Key Authentication”All requests must be authenticated using Ed25519 signatures:
Headers:
Authorization: Pubky <public_key>:<signature>:<timestamp>Signature Generation:
- Create message:
METHOD:PATH:TIMESTAMP:BODY_HASH - Sign message with Ed25519 private key
- Encode signature as base64
Example (conceptual):
Method: PUTPath: /pub/myapp/dataTimestamp: 1704067200Body: {"hello":"world"}Body Hash: sha256(body) = abc123...
Message to sign: "PUT:/pub/myapp/data:1704067200:abc123..."Signature: sign_ed25519(message, private_key)
Authorization: Pubky 8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo:SGVsbG8gV29ybGQ=:1704067200Session Tokens
Section titled “Session Tokens”For long-lived connections, use session tokens:
Request:
POST /auth/sessionAuthorization: Pubky <public_key>:<signature>:<timestamp>Content-Type: application/json
{ "capabilities": [ "read:/pub/", "write:/pub/myapp/" ], "ttl": 3600}Response:
{ "token": "session_abc123...", "expires_at": 1704070800}Usage:
GET /pub/myapp/dataAuthorization: Bearer session_abc123...Storage Endpoints
Section titled “Storage Endpoints”PUT - Store Data
Section titled “PUT - Store Data”Store or update data at a path.
Request:
PUT /:pathAuthorization: Pubky <public_key>:<signature>:<timestamp>Content-Type: application/octet-stream
<binary data>Path Format:
- Must start with
/pub/(public) or/private/(future) - Maximum length: 1024 bytes
- Allowed characters:
a-z,A-Z,0-9,-,_,/,.
Response:
HTTP/1.1 200 OKContent-Type: application/json
{ "path": "/pub/myapp/data", "size": 1234, "created_at": 1704067200}Error Responses:
400 Bad Request: Invalid path or data401 Unauthorized: Invalid authentication403 Forbidden: Insufficient permissions413 Payload Too Large: Data exceeds limit (default: 10MB)507 Insufficient Storage: Quota exceeded
GET - Retrieve Data
Section titled “GET - Retrieve Data”Retrieve data from a path.
Request:
GET /:pathAuthorization: Pubky <public_key>:<signature>:<timestamp>Response:
HTTP/1.1 200 OKContent-Type: application/octet-streamContent-Length: 1234
<binary data>Error Responses:
401 Unauthorized: Invalid authentication403 Forbidden: Insufficient permissions404 Not Found: Path does not exist
DELETE - Remove Data
Section titled “DELETE - Remove Data”Delete data at a path.
Request:
DELETE /:pathAuthorization: Pubky <public_key>:<signature>:<timestamp>Response:
HTTP/1.1 200 OKContent-Type: application/json
{ "path": "/pub/myapp/data", "deleted_at": 1704067200}Error Responses:
401 Unauthorized: Invalid authentication403 Forbidden: Insufficient permissions404 Not Found: Path does not exist
LIST - Enumerate Data
Section titled “LIST - Enumerate Data”List entries under a path prefix (with pagination).
Request:
GET /:path?limit=20&cursor=abc123&reverse=falseAuthorization: Pubky <public_key>:<signature>:<timestamp>Query Parameters:
limit(optional): Maximum entries to return (default: 100, max: 1000)cursor(optional): Pagination cursor from previous responsereverse(optional): List in reverse order (newest first)
Response:
HTTP/1.1 200 OKContent-Type: application/json
{ "entries": [ { "path": "/pub/myapp/posts/001", "size": 512, "created_at": 1704067200, "updated_at": 1704067200 }, { "path": "/pub/myapp/posts/002", "size": 1024, "created_at": 1704067300, "updated_at": 1704067300 } ], "cursor": "next_page_cursor_xyz", "has_more": true}Error Responses:
401 Unauthorized: Invalid authentication403 Forbidden: Insufficient permissions
Capabilities System
Section titled “Capabilities System”Capabilities define what operations a session can perform:
Capability Syntax
Section titled “Capability Syntax”<operation>:<path_prefix>Operations:
read: GET, LIST operationswrite: PUT, DELETE operations*: All operations
Examples:
read:/pub/ # Read all public datawrite:/pub/myapp/ # Write to /pub/myapp/* only*:/pub/myapp/posts/ # Full access to postsread:/pub/social/profile # Read specific pathCapability Checking
Section titled “Capability Checking”When a request is made:
- Check session capabilities
- Match requested path against capability patterns
- Verify operation is allowed
- Execute or deny request
Event Streaming
Section titled “Event Streaming”Subscribe to real-time updates on data changes via Server-Sent Events (SSE). Two endpoints serve different use cases:
GET /events-stream — Real-Time SSE Stream
Section titled “GET /events-stream — Real-Time SSE Stream”The primary event API. Clients subscribe to specific users on a homeserver without processing unwanted traffic.
Request:
GET /events-stream?user=<z32_pubkey>&user=<z32_pubkey>:<cursor>&limit=100&live=true&path=/pub/Query Parameters:
user(required, repeatable): User public key in z32 format. Append:<cursor>to resume from a position (e.g.user=abc123:42). Up to 50 users per requestlimit(optional): Maximum events before closing (1–65535). Without limit andlive=false, all historical events are sent then the stream closeslive(optional): Whentrue, delivers all historical events first, then streams new events in real-time. Cannot combine withreversereverse(optional): Whentrue, delivers events newest-first then closes. Cannot combine withlivepath(optional): Filter events by path prefix (e.g./pub/pubky.app/)
Response (Server-Sent Events):
HTTP/1.1 200 OKContent-Type: text/event-stream
event: PUTdata: pubky://o1gg96ewuojmopcjbz8895478wdtxtzzuxnfjjz8o8e77csa1ngo/pub/posts/003data: cursor: 42data: content_hash: AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=
event: DELdata: pubky://o1gg96ewuojmopcjbz8895478wdtxtzzuxnfjjz8o8e77csa1ngo/pub/tempdata: cursor: 43Event Types:
PUT: Data was created or updated. Includes acontent_hash(base64-encoded Blake3 hash)DEL: Data was deleted
SSE Data Format (one data: line per field):
- First line: full
pubky://resource URL cursor: <u64>— event ID for pagination/resumptioncontent_hash: <base64>— 32-byte Blake3 hash (PUT events only)
GET /events/ — Paginated Event Feed
Section titled “GET /events/ — Paginated Event Feed”Paginated feed of all events across all users on the homeserver. Intended for indexers and aggregators like Pubky Nexus.
Request:
GET /events/?cursor=<event_cursor>&limit=1000Returns up to 1000 events per batch. Use the returned cursor to paginate through the full history.
Signup Token Validation
Section titled “Signup Token Validation”Homeservers that require signup tokens (via Homegate) expose an endpoint to check token validity.
GET /signup_tokens/{token}
Section titled “GET /signup_tokens/{token}”Check whether a signup token is valid, used, or unknown.
Response (200 OK):
{ "status": "valid", "created_at": "2025-03-18T12:00:00Z"}Status values: valid (unused), used (already redeemed)
Error Responses:
400 Bad Request: Missing or invalid token format, or homeserver does not require signup tokens404 Not Found: Token does not exist
Rate Limiting: This endpoint is rate-limited to 10 requests per IP per minute by default.
Admin API
Section titled “Admin API”Each Homeserver runs a separate admin HTTP server on its own socket (default 127.0.0.1:6288), isolated from the public Pubky API. It is the only surface for operator tasks — minting signup tokens, suspending abusive users, adjusting per-user quotas, deleting entries, and inspecting health. The admin listener is plain HTTP, so keep it on localhost or a trusted network and front it with a reverse proxy if it ever needs to leave the host. See Homeserver for the operator-facing overview.
Authentication
Section titled “Authentication”A shared admin password gates every protected route:
- JSON endpoints expect
X-Admin-Password: <password> - The WebDAV mount at
/dav/*uses HTTP Basic auth (admin:<password>), so browsers receive a standardWWW-Authenticateprompt
The password lives at [admin].admin_password in config.toml. The sample config ships with "admin" for local development — replace it before exposing the port.
Endpoints with a {public_key} path parameter return 400 Bad Request if the value is not a valid z32-encoded public key.
GET / — Liveness Probe
Section titled “GET / — Liveness Probe”Returns the literal string "Homeserver - Admin Endpoint". Unauthenticated; useful for basic reachability checks against the admin listener.
GET /info — Server Overview
Section titled “GET /info — Server Overview”Returns the user count, the disabled-user count, total disk usage in MB, signup-code stats, the homeserver public key, the advertised PKARR pubky address and ICANN domain, and the running version.
Response (200 OK):
{ "num_users": 1842, "num_disabled_users": 3, "total_disk_used_mb": 28471, "num_signup_codes": 250, "num_unused_signup_codes": 47, "public_key": "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo", "pkarr_pubky_address": null, "pkarr_icann_domain": "homeserver.example.com", "version": "0.7.0"}pkarr_pubky_address and pkarr_icann_domain are nullable — they reflect the server’s PKARR and ICANN configuration and may be absent.
Signup Tokens
Section titled “Signup Tokens”Mint signup tokens for gated homeservers — see Homegate for the redemption flow.
GET /generate_signup_token mints a token using system-default quotas. Returns the token string in the response body.
POST /generate_signup_token mints a token with explicit per-user quota overrides:
POST /generate_signup_tokenX-Admin-Password: <password>Content-Type: application/json
{ "storage_quota_mb": 1024, "rate_read": "200mb/m"}Each field accepts a value, "unlimited", or null to use the system default. Absent fields fall back to system defaults. Invalid rate strings return 422 Unprocessable Entity.
User Suspension
Section titled “User Suspension”POST /users/{public_key}/disable — flip a per-user disabled flag. Subsequent reads and writes against that user’s data fail until re-enabled.
POST /users/{public_key}/enable — reverse the disable.
Both return 200 OK on success, 404 Not Found for unknown users.
Per-User Quotas
Section titled “Per-User Quotas”GET /users/{public_key}/quota returns both the effective quota (per-user overrides merged with system defaults from [default_quotas] and [storage].default_quota_mb) and the raw overrides:
{ "effective": { "storage_quota_mb": 500, "rate_read": "10mb/s", "rate_write": "5mb/s" }, "overrides": { "storage_quota_mb": 500 }}PATCH /users/{public_key}/quota updates per-user storage and bandwidth fields. Each field follows the same semantics:
- absent → keep existing override
null→ reset to Default (use system default)"unlimited"→ no limit- value (
1024,"100mb/m") → explicit override
Entry Deletion
Section titled “Entry Deletion”DELETE /webdav/{public_key}/pub/... removes a single entry by path and emits a normal DEL event so subscribers stay in sync.
The full /dav/* mount additionally exposes PROPFIND, GET, PUT, and DELETE across all user data for ops-driven inspection or bulk cleanup. It uses HTTP Basic auth (admin:<password>).
Tooling
Section titled “Tooling”The Pubky CLI wraps these endpoints under pubky-cli admin … (info, generate-token, user disable, user enable, user delete) and reads the password from PUBKY_ADMIN_PASSWORD.
Configuration
Section titled “Configuration”[admin]enabled = truelisten_socket = "127.0.0.1:6288"admin_password = "change-me"Metrics Endpoint
Section titled “Metrics Endpoint”Prometheus-compatible metrics for monitoring.
GET /metrics
Section titled “GET /metrics”Response:
# HELP pubky_requests_total Total HTTP requests# TYPE pubky_requests_total counterpubky_requests_total{method="GET",status="200"} 1000pubky_requests_total{method="PUT",status="200"} 500
# HELP pubky_storage_bytes Total storage used# TYPE pubky_storage_bytes gaugepubky_storage_bytes 1073741824
# HELP pubky_active_sessions Current active sessions# TYPE pubky_active_sessions gaugepubky_active_sessions 50Rate Limiting
Section titled “Rate Limiting”Homeservers implement rate limiting to prevent abuse:
Headers:
X-RateLimit-Limit: 100X-RateLimit-Remaining: 95X-RateLimit-Reset: 1704067260Rate Limit Exceeded:
HTTP/1.1 429 Too Many RequestsRetry-After: 60
{ "error": "rate_limit_exceeded", "message": "Too many requests, try again in 60 seconds"}Default Limits:
- Anonymous: 10 requests/minute
- Authenticated: 100 requests/minute
- Admin: Unlimited
Error Responses
Section titled “Error Responses”All errors follow a consistent format:
{ "error": "error_code", "message": "Human-readable error message", "details": { "additional": "context" }}Common Error Codes:
invalid_path: Path format is invalidinvalid_signature: Authentication signature invalidexpired_session: Session token expiredinsufficient_permissions: Operation not allowedstorage_quota_exceeded: User quota exceededrate_limit_exceeded: Too many requestsserver_error: Internal server error
Best Practices
Section titled “Best Practices”Optimize Storage
Section titled “Optimize Storage”Store structured data efficiently:
// Good: Separate entries for each postPUT /pub/myapp/posts/001 (small JSON)PUT /pub/myapp/posts/002 (small JSON)PUT /pub/myapp/posts/003 (small JSON)
// Bad: Single large entryPUT /pub/myapp/all_posts (large JSON array)Handle Rate Limits
Section titled “Handle Rate Limits”async function putWithRetry(session, path, data, retries = 3) { for (let i = 0; i < retries; i++) { try { return await session.storage.putText(path, data); } catch (error) { if (error.status === 429) { // Too Many Requests await new Promise(r => setTimeout(r, 1000 * (i + 1))); continue; } throw error; } }}Resources
Section titled “Resources”- Pubky Core Overview: Main documentation
- SDK Documentation: Client libraries
- Homeserver Documentation: Server setup
- Official Docs: pubky.github.io/pubky-core
- Repository: github.com/pubky/pubky-core
The Pubky Core API provides a simple, RESTful interface for decentralized data storage.