REST API
Read-only HTTP API for querying stored events at GET /api/v1/... — served on the same port as the WebSocket relay.
Base URL
The API is served under /api/v1 on the same port as the WebSocket relay:
http://<host>:<port>/api/v1/{identifier}
http://<host>:<port>/api/v1/{identifier}/{kind}Host routing (server.api_host)
When server.api_host (e.g. api.example.com) is configured, the API and the
relay are split by the Host header: api.example.com gets /api/v1, /health and /metrics; any other host gets the WebSocket relay and NIP-11.
Without api_host the API is served on every host. Only GET is supported — WebSocket
upgrade requests to /api/v1 are refused with 403.
Endpoints
Identifier-based paths
| Path | Returns |
|---|---|
GET /api/v1/<npub1...> | Latest kind-0 profile event |
GET /api/v1/<note1> / <nevent1> | The single event with this id |
GET /api/v1/<naddr1> | Events of the address (kind + author + d tag) |
GET /api/v1/<npub1...>/<kind> | Events by pubkey, filtered by kind (only valid for npub1...; 400 otherwise) |
Query and aggregate endpoints
GET /api/v1/query— generic filter query without an identifier.GET /api/v1/count— total count for the same filter parameters (NIP-45 semantics).GET /api/v1/<npub1...>/kinds— per-kind event counts for an author, most used first.GET /api/v1/<npub1...>/<kind>/daily— per-day counts for one month; month must be 1-12, and every day is reported zero-filled through the last day.GET /api/v1/ids/<hex>— a single event by its 64-hex id (prefixes rejected).GET /api/v1/<npub1...>/stats— author summary (total, first/last activity, kind breakdown).GET /api/v1/<npub1...>/<kind>/hourly— per-hour counts for one day; all 24 hours are reported, zero-filled.GET /api/v1/ids/<hex>/related— replies (#e) and quotes (#q) referencing the event.GET /api/v1/<npub1...>/follows— the author's latest kind-3 follow list.GET /api/v1/relay/kinds— the most common kinds on the relay (bounded, visibility-filtered sample).GET /api/v1/relay/top-authors— the most active authors on the relay (bounded, visibility-filtered sample; approximate flag).GET /api/v1/<npub1...>/relays— the author's latest NIP-65 relay list (kind 10002).GET /api/v1/<npub1...>/<kind>/monthly— per-month counts, zero-filled over the since/until range (default: the whole period; capped at 120 months).
Query parameters
| Parameter | Description |
|---|---|
limit | Max results (default 100, capped by max_api_limit) |
offset | Number of visible results to skip (pagination) |
since | Only events with created_at >= since |
until | Only events with created_at <= until |
sort | asc/ascending for oldest first; default is newest first |
search | NIP-50 full-text search (whole-word matching) |
e / p / t / d | Filter by #e / #p / #t / #d tags |
no_p / no_e / no_t / no_d | Exclude events carrying that tag — applied before pagination, so excluded events never consume limit slots or offset steps |
Response format
Successful responses return 200 OK with the following JSON body:
{
"events": [
{
"id": "32-byte hex event id",
"pubkey": "32-byte hex pubkey",
"created_at": 1700000000,
"kind": 1,
"tags": [["t", "example"]],
"content": "hello",
"sig": "64-byte hex signature"
}
],
"count": 1,
"more": false
}| Field | Description |
|---|---|
events | The events of this page (newest first by default) |
count | The number of events in this page |
more | True when further pages exist (use offset to fetch them) |
Pagination
Pagination is done with offset and the more flag, computed over the visible sequence — hidden events never skip or duplicate a page:
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/1?limit=50&offset=0" # page 1
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/1?limit=50&offset=50" # page 2 (when more was true)Visibility rules
The API is unauthenticated, so it withholds the same events as an anonymous WebSocket connection:
- NIP-70 protected events (carrying a
-tag) - NIP-59 gift wraps (kind 1059)
- NIP-29 private/hidden group content (visible only to members)
Errors and status codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Invalid identifier or query parameter |
| 403 | WebSocket upgrade attempt to /api/v1 |
| 404 | Unknown path, or wrong Host for the API (api_host configured) |
| 503 | API concurrency limit reached — retry shortly |
Examples
Fetch a user's notes (newest first):
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/1"Paginate and sort:
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/1?limit=10&offset=10&sort=asc"Fetch a single event by id (note1... or nevent1... both work):
curl "http://127.0.0.1:8080/api/v1/note1..."
curl "http://127.0.0.1:8080/api/v1/nevent1..."Fetch an addressable event (naddr1...):
curl "http://127.0.0.1:8080/api/v1/naddr1..."Search:
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/1?search=rust"Tag filter:
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/7?e=<event-id>&limit=100"