1---2title: HTTP reference3audience: integrator4---56# HTTP reference78Every path the worker and a relay answer. Paths are on `https://<name>.bind.ws` unless marked apex. One read rule covers every door that shows events, files, names or presence: where the auth column says "the read rule", a relay whose reads are *anyone* answers without a signature, one whose reads are *signed in* takes any valid signature, and one whose reads are *members* takes a member's. Auth column: "NIP-98" is a signed request (see [Scripts and agents](13-scripts-and-agents.md)); "Blossom" is a kind 24242 token in the Authorization header; "none" is public. JSON errors carry `{ "error": "<prefix>: reason" }`; Blossom and NIP-96 errors also set an `X-Reason` header.910Requests with `Accept: application/nostr+json` answer the NIP-11 document,11except explicit NIP-AD path discovery. A websocket upgrade on any relay12path opens the relay. A blocked address is refused with 403 on the socket13and on the doors that write, read or serve files; the page, NIP-11 and14management stay open.1516## Apex1718| Path | Method | Auth | Answers | Status |19|---|---|---|---|---|20| `/` | GET | none | the landing page | 200 |21| `/lease` | POST | NIP-98 optional | `{ name, url, console, expires_at, days, holder?, claim }`; a signature reserves the claim for that key | 201; 401 bad signature; 429 rate limited, five a minute per address, 60 overall; 503 no free name |22| `/favicon.svg` | GET | none | the icon | 200 |23| `/relay-config.schema.json` | GET | none | the JSON Schema of a relay configuration file, for `$schema` ([Scripts and agents](13-scripts-and-agents.md#the-configuration-file)) | 200 |24| `/connection-template.schema.json` | GET | none | the JSON Schema of a connection template, for `$schema` ([Connection templates](17-connection-templates.md)) | 200 |25| `/.well-known/bindws/hostname?domain=<host>` | GET, on any host | none | whether the hostname is one of ours: the apex, a valid name under it, or a mapped custom hostname; no body. For a proxy issuing certificates on demand ([Hosting without Cloudflare](16-hosting-without-cloudflare.md)) | 200 ours; 404 not |2627## Relay page and information2829| Path | Method | Auth | Answers | Status |30|---|---|---|---|---|31| `/` | GET | none | the console, one page for visitors and owner alike | 200 |32| `/` | GET with `Accept: application/nostr+json` | none | NIP-11: rules, limits, retention, `self`, `lease` while leased, `succession_pending` while warning | 200 |33| `/` | POST, `content-type: application/nostr+json+rpc` | NIP-98 | NIP-86 management, `{ result }` or `{ error }` | 200; 400 invalid; 401 bad signature; 403 not allowed; 409 conflict; 429 active relay operation |34| `/people` | GET | none | `{ public, self, host, people }`, the members when the directory is public | 200 |35| `/.well-known/nostr.json?path=<pathname>` | GET, HEAD; OPTIONS preflight | the read rule for the group; public pages only | NIP-AD path-keyed `{ filter, relays }`, or `{}` without a mapping; [NIP-AD web addresses](23-nip-ad-web-addresses.md) | 200; 400 invalid pathname; 401/403 group read rule; 405 other methods |36| `/terms` | GET | none | the join terms as a page | 200; 404 none set |37| `/signer.js` | GET | none | the NIP-46 client bundle, cached a week | 200 |38| `/favicon.svg` | GET | none | the icon | 200 |39| any | OPTIONS | none | CORS preflight | 200 |4041A door that belongs to a feature the owner switched off (Relay configuration, Rules) answers 404: names, files (Blossom and NIP-96), pages and the feed. `supported_nips` drops the numbers of features that are off.4243When GRASP is on and reads are open, NIP-11 also has a `supported_grasps` list44containing `GRASP-01` and validated `GRASP-06` when enabled. GRASP-02/03/0545remain bounded previews pending interoperability and conformance review. The46document also has a `repo_acceptance_criteria` string and, only47for policy beyond ordinary spam controls, `curation`. See [GRASP-01 Git48hosting](22-grasp-01-git-hosting.md) for the acceptance and Git protocol rules.4950## NIP-5A sites5152Site hostnames are `https://<npub>.<domain>`,53`https://<pubkeyB36><dTag>.<domain>` and54`https://v<snapshotIdB36>.<domain>`. The label is resolved to the relay that55holds the manifest, and the request reaches only the site door. Site hosting56follows the `sites` feature and the relay's read rule.5758| Path | Method | Auth | Answers | Status |59|---|---|---|---|---|60| `/` or a site path | GET, HEAD | the read rule | the manifest's file; directory paths select `index.html` | 200; 304 with a matching ETag; 401/403 by the read rule; 404 missing manifest, path or blob |61| `/` or a site path | other methods or websocket upgrade | none | method error | 405 |62| `/.well-known/nsite/auth` | GET | none, or NIP-98 when continuing an API session | NIP-07 sign-in HTML with a five-minute challenge | 401; 429 too many outstanding challenges |63| `/.well-known/nsite/auth` | POST | exact NIP-98 for this URL, method and body; body contains the signed kind 22242 challenge response | sets the seven-day `__Host-nsite` cookie | 204; 401 invalid or expired proof; 403 read rule; 413 body over 32 KiB |64| `/.well-known/nostr.json?path=<pathname>` | GET, HEAD; OPTIONS preflight | the site's read rule | NIP-AD mapping to the live manifest with its hosting relay hint; no file fetch | 200; 204 preflight; 400 invalid pathname; 401/403 read rule; 404 unavailable site; 405 other methods |6566Site responses include `Content-Type`, `Content-Length` when supplied by the67source, an ETag equal to the file hash, `X-Content-Type-Options: nosniff`, and68a cache policy based on the read rule. A missing path uses `/404.html` when69the manifest contains it; a missing manifest, unavailable file, or failed70hash check returns a plain 404. On a local cache miss the door may proxy the71file from up to ten manifest servers followed by up to ten author kind 1006372servers, subject to a cap of `min(maxBlobMB, 32 MiB)`. Only public HTTPS URLs73and checked redirects are used, and caller credentials are not forwarded.7475The sign-in cookie is Secure, HttpOnly and SameSite=Lax. The site door checks76it against the current read rule on every request. A caller can use exact77NIP-98 directly instead of a cookie.7879## GRASP-01 Git8081When the feature is on, reads are open and the repository announcement is82accepted, the percent-encoded repository path exposes Git Smart HTTP. It uses83public HTTP with signed NIP-34 state authorizing writes. A restricted read84rule disables this door rather than offering authenticated private Git.8586| Path | Method | Auth | Answers | Status |87|---|---|---|---|---|88| `/<npub>/<identifier>.git` | GET, HEAD | public | repository page | 200; 404 unhosted repository |89| `/<npub>/<identifier>.git/info/refs?service=git-upload-pack` or `service=git-receive-pack` | GET | public | service and ref advertisement | 200; 400 unsupported service |90| `/<npub>/<identifier>.git/git-receive-pack` | POST | signed NIP-34 state and PR rules | receive-pack report | 200, including protocol-level rejection; 409 rejection without report-status; 400 malformed request; 413 request limit; 415 media type |91| `/<npub>/<identifier>.git/git-upload-pack` | POST | public | bounded pack for reachable, tip or filtered wants | 200; 400 malformed or invalid want; 413 request or response limit; 415 media type |92| a Git path | OPTIONS | none | CORS preflight | 204 |9394The door also returns 403 for a restricted read rule, inactive relay or95exhausted fuel; 429 for rate limits or an operation already in progress; and96503 when repository storage is unavailable. Receive-pack clients must read97the Git report: HTTP 200 alone does not mean a push was accepted.9899Git responses include `Access-Control-Allow-Origin: *`,100`Access-Control-Allow-Methods: GET, POST` and101`Access-Control-Allow-Headers: Content-Type, Authorization, Git-Protocol, X-Git-Request-Id`.102Upload-pack advertises `allow-reachable-sha1-in-want`,103`allow-tip-sha1-in-want` and `filter`, and accepts `blob:none` and `tree:0`.104An unknown `refs/nostr/<event-id>` has a 20-minute holding window. Expired105unmatched refs are hidden and scheduled for deletion, but immutable Git106objects remain in retained storage. Expiring a ref does not make those bytes107free.108109The owner can inspect one accepted repository through the NIP-86110`gitstorage` method at the relay root. It performs a bounded, read-only walk111of SQLite metadata and reports the backend, object count, raw bytes, compressed112bytes, metadata bytes, ref count, receipt count and the relay's complete113`physicalDatabaseBytes`. The physical database total includes all relay data,114indexes and Git data, so the diagnostic object bytes are not billed again.115The method reports zero R2 gets and lists for this backend. Its SQL metadata116reads contribute to the ordinary row meter. It changes no stored objects, refs117or receipts and has a 60-second per-instance cooldown; a Durable Object118restart or eviction resets that cooldown.119120| Method | Parameters | Auth | Answers | Status |121|---|---|---|---|---|122| `gitstorage` | repository owner hex pubkey, identifier | NIP-98, owner with the storage action | bounded SQLite object, ref and receipt inventory, physical database size and limits | 200; 400 invalid parameters; 403 inactive, fuel or role; 404 feature or repository unavailable; 429 cooldown or another repository operation |123124The owner-only `storagestats` method also includes `result.graspSync` when125GRASP synchronization is configured. It reports `enabled`, `partial`, event126counts (`jobs`, `failed`, `history`, `due`) and Git counts (`jobs`, `failed`,127`due`). `due` is the earliest Unix timestamp or `null`; `failed` counts rows128with durable error details, and `history` counts unfinished history windows.129130## Custom domains131132These NIP-86 methods are owner-only and use the relay's normal NIP-98 RPC133endpoint. The optional site label uses the same root, named-site and snapshot134grammar as the hostname forms above. An omitted or empty label targets the135relay itself. `listdomains` includes the current `site` field.136137| Method | Parameters | Answers | Status |138|---|---|---|---|139| `adddomain` | `host`, optional `site` label | creates the custom hostname and maps it to the relay or selected site | 200; 400 invalid or duplicate; 403; 502 Cloudflare failure |140| `setdomainsite` | `host`, optional `site` label | changes an existing hostname's destination | 200; 400 invalid host or site; 403 |141| `listdomains` | none | custom host records, including `site` when selected | 200; 403 |142143## Bridge144145Signed with NIP-98 over the exact URL, method and body. Same gates as a socket: the signer's bans, the read rule and the write rules apply, and the address is rate limited at four times the per-connection allowance.146147| Path | Method | Body | Answers | Status |148|---|---|---|---|---|149| `/events` | POST | one signed event | `{ event_id, accepted, message }` | 200 accepted; 400 refused, the message says why |150| `/query` | POST | a non-empty list of filters | the events, newest first, up to the relay's query limit | 200; 400 bad filter; 403 read rule |151| `/count` | POST | a non-empty list of filters | `{ count }` | 200; 400; 403 |152153## Files: Blossom154155| Path | Method | Auth | Answers | Status |156|---|---|---|---|---|157| `/upload` | HEAD | Blossom `upload` | whether an upload with the `X-SHA-256`, `X-Content-Type` and `X-Content-Length` headers would be accepted | 200; 400 bad headers; 401; 403 the write gate; 411 no length; 413 too big |158| `/upload` | PUT | Blossom `upload` | the descriptor with `nip94` tags | 200 exists; 201 stored; 400; 401; 403 gate or a removed hash; 413 |159| `/mirror` | PUT | Blossom `upload` | copies the blob at `{ "url": ... }` from another server, the descriptor | 200 exists; 201 stored; 400; 401; 403; 409 hash mismatch with the token; 413; 502 origin failed |160| `/report` | PUT | none, the body is a signed kind 1984; the reporter must pass the read rule | files a report for each `x` tag the relay holds | 200; 400 bad event or older than an hour; 401 reporter must sign in under a read rule; 403 unclaimed relay, banned reporter or reporter not admitted by the read rule; 404 no such blob |161| `/list/<pubkey>` | GET | the read rule: none while reads are open, else Blossom `list` or NIP-98 | that uploader's descriptors | 200; 400 bad pubkey; 401; 403 |162| `/<sha256>[.ext]` | GET, HEAD | the read rule: none while reads are open, else Blossom `get` or NIP-98 | the blob, ranges honoured; not cacheable by shared caches when gated | 200; 206; 401; 403; 404 |163| `/<sha256>` | DELETE | Blossom `delete` | removes it; the uploader or the owner | 204; 400 token names another blob; 401; 403; 404 |164165## Files: NIP-96166167The same bucket and file list through the NIP-96 shape. Answers are `{ status, message, ... }` and errors set `X-Reason`.168169| Path | Method | Auth | Answers | Status |170|---|---|---|---|---|171| `/.well-known/nostr/nip96.json` | GET | none | `api_url`, `download_url`, `supported_nips`, content types, the free plan with the size cap | 200 |172| `/nip96` | POST, multipart with a `file` field, optional `size` and `caption` | NIP-98, `payload` is the file's hash | the NIP-94 event in `nip94_event` and the Blossom URL | 200; 400; 401; 403; 413 |173| `/nip96?page=&count=` | GET | NIP-98 | the caller's files | 200; 401 |174| `/nip96/<sha256>` | GET, HEAD | the read rule, as the Blossom path | the blob, same as the Blossom path | 200; 401; 403; 404 |175| `/nip96/<sha256>` | DELETE | NIP-98 | removes it; the uploader or anyone with the storage action | 200; 401; 403; 404 |176177## Pages and feed178179Only while reads are open. Otherwise every path here answers 404, and an unclaimed relay answers 404 too. Cached five minutes.180181| Path | Method | Answers |182|---|---|---|183| `/e/<id>` | GET | a note as a page with Open Graph tags |184| `/a/<d>` | GET | the owner's article with that identifier |185| `/a/<npub or hex>/<d>` | GET | anyone's article |186| `/feed.xml` | GET | Atom, the newest 50 notes and articles; `?kinds=1` or `?kinds=30023` to pick one, `?author=<hex>` for one person |187188## Card and QR189190| Path | Method | Answers | Status |191|---|---|---|---|192| `/card.json` | GET | `name`, `state`, `url`, `console`; on a claimed relay also `description`, `icon`, `owner`, `self`, `members` when the directory is public, `reads`, `writes`, `fuel`, `naddr`, `nprofile` (the owner with this relay as the hint), `signed_url`, `image`; a lease adds `expires_at` | 200 |193| `/card.nostr` | GET | the same facts as a kind 30078 signed by the relay's key | 200; 404 no owner |194| `/card.svg` | GET | a 600 by 315 picture with the naddr as a QR | 200 |195| `/qr.svg?text=` | GET | any text up to 512 bytes as a QR | 200; 400 empty; 413 too long |196197## Connect198199| Path | Method | Auth | Answers | Status |200|---|---|---|---|---|201| `/connect.json` | GET | NIP-98 optional | `{ relay: { url, host, web, name }, viewer, connections }`: the Connect fold's shortcuts the asker may see, links filled in for them, each `{ template, title, about, app, where, icon, visibility, links, qr, needsUser }`. Unsigned, the public ones, with the links that need a viewer left out and `needsUser` set, cached a minute; signed, that key's view, `no-store` ([Connection templates](17-connection-templates.md)) | 200; 401 bad signature |202203## People, invites and names204205The NIP-86 `createclaim`, `listclaims` and `deleteclaim` methods use `POST /` to manage the same codes redeemed below. Their parameters, results and limits are in [NIP-86 membership claims](24-nip86-claims.md); the bind.ws `claim` method still concerns relay ownership.206207| Path | Method | Auth | Answers | Status |208|---|---|---|---|---|209| `/.well-known/nostr.json?name=` | GET | none for a name; the directory switch for the listing without one, else NIP-98 by a member | NIP-05 for the member with that name, with this relay as their relay; without a name, every named member when the directory is public | 200; 401 bad signature |210| `/invite/<code>` | GET | none | the invite page, with the join terms | 200 |211| `/api/join-policy` | GET | none | `{ terms }` | 200 |212| `/api/invites/claim` | POST `{ "code": ... }` | NIP-98 | `{ status: "joined" }` or `{ status: "already_member", role }` | 200; 400; 401; 403 invalid, used up or expired, or banned |213214## Dumps215216| Path | Method | Auth | Answers | Status |217|---|---|---|---|---|218| `/dumps/<name>.jsonl` | GET | NIP-98 with the storage action | the dump, one event per line | 200; 400 bad name; 401; 403; 404 |219220## Fuel221222| Path | Method | Auth | Answers | Status |223|---|---|---|---|---|224| `/fuel` | GET | none | meters, allowances, prices and balance; who zapped is in the `stats` management method | 200 |225| `/fuel/invoice` | POST `{ "zapRequest": <kind 9734> }` | none | `{ invoice, providerPubkey, msats }` from the lightning provider | 200; 400 bad request or amount; 502 provider |226227## Views228229| Path | Method | Auth | Answers | Codes |230|---|---|---|---|---|231| `/view/<name>` | GET | none for a public view; NIP-98 by a member for a members-only one; `zaps`, `moderation`, `calendar`, `articles` and `presence` follow the read rule, `profiles` and `relays` the directory switch | the view's latest signed kind 30078 record as JSON, or presence as a kind 20078 from memory | 200; 401 `auth-required:` when a members-only view is asked for without a signature; 403 when the signer is not a member; 404 when the view is off, unknown, or has not run yet |232233Names: `profiles`, `relays`, `calendar`, `moderation`, `articles`, `zaps`, `presence`. The information document lists the ones a relay keeps under `views`, with each one's kind, `d`, trigger and audience.234235## Websocket236237The relay itself: `wss://<name>.bind.ws`. NIP-01 with 09, 13, 17 and 59 as private kinds, 29 and 43 for the group, 40, 42, 45, 50, 56, 62, 66 as a kind 30166 record signed by the relay about itself, 70 and 77, an `AUTH` challenge on connect, and `EOSE` hints per NIP-67. Kind 24133 passes the write and read rules so a NIP-46 signer or client can use the relay as transport: it is delivered to a socket that has proved a party's key with `AUTH`, or to a subscription that names one in `#p` or `authors`, and never to a bare subscription to the kind.