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

All API endpoints are relative to the Homeserver base URL:

https://homeserver.example.com

Homeserver URLs are discovered via PKARR records published to the Mainline DHT.

Authentication

See Authentication for conceptual overview.

Public Key Authentication

All requests must be authenticated using Ed25519 signatures:

Headers:

Authorization: Pubky <public_key>:<signature>:<timestamp>

Signature Generation:

  1. Create message: METHOD:PATH:TIMESTAMP:BODY_HASH
  2. Sign message with Ed25519 private key
  3. Encode signature as base64

Example (conceptual):

Method: PUT
Path: /pub/myapp/data
Timestamp: 1704067200
Body: {"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=:1704067200

Session Tokens

For long-lived connections, use session tokens:

Request:

POST /auth/session
Authorization: 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/data
Authorization: Bearer session_abc123...

Storage Endpoints

PUT - Store Data

Store or update data at a path.

Request:

PUT /:path
Authorization: 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 OK
Content-Type: application/json
 
{
  "path": "/pub/myapp/data",
  "size": 1234,
  "created_at": 1704067200
}

Error Responses:

  • 400 Bad Request: Invalid path or data
  • 401 Unauthorized: Invalid authentication
  • 403 Forbidden: Insufficient permissions
  • 413 Payload Too Large: Data exceeds limit (default: 10MB)
  • 507 Insufficient Storage: Quota exceeded

GET - Retrieve Data

Retrieve data from a path.

Request:

GET /:path
Authorization: Pubky <public_key>:<signature>:<timestamp>

Response:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 1234
 
<binary data>

Error Responses:

  • 401 Unauthorized: Invalid authentication
  • 403 Forbidden: Insufficient permissions
  • 404 Not Found: Path does not exist

DELETE - Remove Data

Delete data at a path.

Request:

DELETE /:path
Authorization: Pubky <public_key>:<signature>:<timestamp>

Response:

HTTP/1.1 200 OK
Content-Type: application/json
 
{
  "path": "/pub/myapp/data",
  "deleted_at": 1704067200
}

Error Responses:

  • 401 Unauthorized: Invalid authentication
  • 403 Forbidden: Insufficient permissions
  • 404 Not Found: Path does not exist

LIST - Enumerate Data

List entries under a path prefix (with pagination).

Request:

GET /:path?limit=20&cursor=abc123&reverse=false
Authorization: Pubky <public_key>:<signature>:<timestamp>

Query Parameters:

  • limit (optional): Maximum entries to return (default: 100, max: 1000)
  • cursor (optional): Pagination cursor from previous response
  • reverse (optional): List in reverse order (newest first)

Response:

HTTP/1.1 200 OK
Content-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 authentication
  • 403 Forbidden: Insufficient permissions

Capabilities System

Capabilities define what operations a session can perform:

Capability Syntax

<operation>:<path_prefix>

Operations:

  • read: GET, LIST operations
  • write: PUT, DELETE operations
  • *: All operations

Examples:

read:/pub/                    # Read all public data
write:/pub/myapp/             # Write to /pub/myapp/* only
*:/pub/myapp/posts/           # Full access to posts
read:/pub/social/profile      # Read specific path

Capability Checking

When a request is made:

  1. Check session capabilities
  2. Match requested path against capability patterns
  3. Verify operation is allowed
  4. Execute or deny request

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

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 request
  • limit (optional): Maximum events before closing (1–65535). Without limit and live=false, all historical events are sent then the stream closes
  • live (optional): When true, delivers all historical events first, then streams new events in real-time. Cannot combine with reverse
  • reverse (optional): When true, delivers events newest-first then closes. Cannot combine with live
  • path (optional): Filter events by path prefix (e.g. /pub/pubky.app/)

Response (Server-Sent Events):

HTTP/1.1 200 OK
Content-Type: text/event-stream
 
event: PUT
data: pubky://o1gg96ewuojmopcjbz8895478wdtxtzzuxnfjjz8o8e77csa1ngo/pub/posts/003
data: cursor: 42
data: content_hash: AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=
 
event: DEL
data: pubky://o1gg96ewuojmopcjbz8895478wdtxtzzuxnfjjz8o8e77csa1ngo/pub/temp
data: cursor: 43

Event Types:

  • PUT: Data was created or updated. Includes a content_hash (base64-encoded Blake3 hash)
  • DEL: Data was deleted

SSE Data Format (one data: line per field):

  1. First line: full pubky:// resource URL
  2. cursor: <u64> — event ID for pagination/resumption
  3. content_hash: <base64> — 32-byte Blake3 hash (PUT events only)

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=1000

Returns up to 1000 events per batch. Use the returned cursor to paginate through the full history.

Signup Token Validation

Homeservers that require signup tokens (via Homegate) expose an endpoint to check token validity.

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 tokens
  • 404 Not Found: Token does not exist

Rate Limiting: This endpoint is rate-limited to 10 requests per IP per minute by default.

Admin Endpoints

Homeserver administrators can access management endpoints:

GET /admin/stats

Get server statistics.

Response:

{
  "users": 1000,
  "total_storage": 1073741824,
  "requests_per_minute": 150,
  "uptime_seconds": 86400
}

GET /admin/users/:public_key

Get user information.

Response:

{
  "public_key": "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo",
  "storage_used": 10485760,
  "storage_quota": 104857600,
  "created_at": 1704000000,
  "last_activity": 1704067200
}

See Admin API for complete admin documentation.

Metrics Endpoint

Prometheus-compatible metrics for monitoring.

GET /metrics

Response:

# HELP pubky_requests_total Total HTTP requests
# TYPE pubky_requests_total counter
pubky_requests_total{method="GET",status="200"} 1000
pubky_requests_total{method="PUT",status="200"} 500

# HELP pubky_storage_bytes Total storage used
# TYPE pubky_storage_bytes gauge
pubky_storage_bytes 1073741824

# HELP pubky_active_sessions Current active sessions
# TYPE pubky_active_sessions gauge
pubky_active_sessions 50

Rate Limiting

Homeservers implement rate limiting to prevent abuse:

Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704067260

Rate Limit Exceeded:

HTTP/1.1 429 Too Many Requests
Retry-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

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 invalid
  • invalid_signature: Authentication signature invalid
  • expired_session: Session token expired
  • insufficient_permissions: Operation not allowed
  • storage_quota_exceeded: User quota exceeded
  • rate_limit_exceeded: Too many requests
  • server_error: Internal server error

Best Practices

Optimize Storage

Store structured data efficiently:

// Good: Separate entries for each post
PUT /pub/myapp/posts/001  (small JSON)
PUT /pub/myapp/posts/002  (small JSON)
PUT /pub/myapp/posts/003  (small JSON)

// Bad: Single large entry
PUT /pub/myapp/all_posts  (large JSON array)

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


The Pubky Core API provides a simple, RESTful interface for decentralized data storage.