REST API reference

Read-only HTTP API for querying stored Nostr events at GET /api/v1/... — endpoints, parameters, pagination, visibility rules and errors.

Base URL

The API is served under /api/v1 on the same port as the WebSocket relay:

text
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

PathReturns
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 (accepts npub1... or nprofile1...; 400 otherwise)

Author identifiers accept npub1..., nprofile1... or a 64-hex pubkey (case-insensitive) on every endpoint.

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 (each entry and the total carry an approximate flag).
  • 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); first_seen/last_seen/months are null when no visible events exist.
  • GET /api/v1/<npub1...>/<kind>/hourly — per-hour counts for one day; all 24 hours are reported, zero-filled (same approximate flags as daily).
  • GET /api/v1/ids/<hex>/related — replies (#e) and quotes (#q) referencing the event; the path id is lowercased before matching, and an e query parameter is OR-ed into the #e side.
  • 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; approximate and filtered flags).
  • GET /api/v1/relay/top-authors — the most active authors on the relay (bounded, visibility-filtered sample; approximate and filtered flags).
  • 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

ParameterDescription
limitMax results (default 100, capped by max_api_limit)
offsetNumber of visible results to skip (pagination)
sinceOnly events with created_at >= since
untilOnly events with created_at <= until
sortasc/ascending for oldest first; default is newest first
searchNIP-50 full-text search (whole-word matching)
e / p / t / dFilter by #e / #p / #t / #d tags
no_p / no_e / no_t / no_dExclude 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:

json
{
  "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
}
FieldDescription
eventsThe events of this page (newest first by default)
countThe number of events in this page
moreTrue 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:

sh
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)

A page is also cut when it reaches the response byte budget (limits.max_req_response_bytes): the response then still reports more: true, so continue from the returned count.

Endpoint quirks
Singleton endpoints (profile, /ids/{hex}, follows, relays) still accept offset — ?offset=1 skips the only event and returns []. The authors/kinds query parameters only filter the generic /query endpoint: on kind endpoints they are silently ignored (both are pre-filled), while on id endpoints they are AND-ed. The stats kind breakdown is ordered by kind, unlike /kinds (count first).

Visibility rules

The API applies the same per-event visibility rules as an anonymous WebSocket connection, and relay.require_auth gates it like one: every request must then carry a NIP-98 Authorization: Nostr <base64 kind:27235 event> whose u tag is the exact request URL (path and query; with server.api_host set, that host's URL) and whose method tag is GET; the event must be fresh (60 seconds) and each authorization is accepted once. Anonymous requests are refused with 401 {"error":"auth-required: this relay requires NIP-98 authorization"}. An authorized caller still sees the anonymous-visibility event set — the API has no identity-scoped reads. The following events are withheld:

  • NIP-70 protected events (carrying a - tag) — except the relay's own relay-signed events, which stay readable
  • NIP-59 gift wraps (kind 1059)
  • NIP-78 application-specific events (kinds 78/30078, owner-only while relay.enabled_nip78_auth is set)
  • NIP-29 private/hidden group content (visible only to members)

Errors and status codes

CodeMeaning
200Success
400Invalid identifier or query parameter
401NIP-98 authorization required (relay.require_auth is set) — missing, invalid, expired or already-used authorization
403WebSocket upgrade attempt to /api/v1
404Unknown path, or wrong Host for the API (api_host configured)
503API concurrency limit reached — retry shortly

Examples

Fetch a user's notes (newest first):

sh
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/1"

Paginate and sort:

sh
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):

sh
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...):

sh
curl "http://127.0.0.1:8080/api/v1/naddr1..."

Search:

sh
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/1?search=rust"

Tag filter:

sh
curl "http://127.0.0.1:8080/api/v1/npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkws3w8ktc/7?e=<event-id>&limit=100"