You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

170 lines
7.6 KiB

Feature: Nostr Relay REST API
As a Nostr client (e.g. Jumble at https://jumble.imwald.eu/)
I want to interact with the relay over HTTP
So that I can publish and query Nostr events
Background:
Given the relay is running at http://localhost:4000
# ---------------------------------------------------------------------------
# Discovery
# ---------------------------------------------------------------------------
Scenario: Client discovers available endpoints
When the client sends GET /api
Then the response status is 200
And the response lists the available endpoints
# ---------------------------------------------------------------------------
# Publishing events
# ---------------------------------------------------------------------------
Scenario: Client publishes a valid kind 1 note
Given a valid signed kind 1 event from keypair 1
When the client sends POST /api/events with the event body
Then the response status is 201 Created
And the response body contains the published event
Scenario: Client publishes a kind 0 profile event with metadata
Given a valid signed kind 0 event containing a JSON metadata content field
When the client sends POST /api/events with the event body
Then the response status is 201 Created
Scenario: Relay rejects a duplicate event
Given a valid signed event has already been published
When the client sends POST /api/events with the same event again
Then the response status is 409 Conflict
Scenario: Relay rejects an event with an invalid ID
Given a signed event whose ID does not match the hash of its content
When the client sends POST /api/events with the event body
Then the response status is 400 Bad Request
Scenario: Relay rejects a NIP-70 protected event
Given a valid signed event with a ["-"] protection tag
When the client sends POST /api/events with the event body
Then the response status is 400 Bad Request
And the response error contains "auth-required"
# ---------------------------------------------------------------------------
# Deleting events (DELETE /api/events/:id)
# ---------------------------------------------------------------------------
Scenario: Client deletes an existing event by ID
Given a known event has been published
When the client sends DELETE /api/events/<event_id>
Then the response status is 204 No Content
Scenario: Deleting a non-existent event returns 404
When the client sends DELETE /api/events/<unknown_id>
Then the response status is 404 Not Found
# ---------------------------------------------------------------------------
# Fetching events (GET /api/events — cacheable, requires since/until/limit)
# ---------------------------------------------------------------------------
Scenario: Client fetches events using time-bounded query params
Given two kind 1 notes have been published
When the client sends GET /api/events?since=0&until=9999999999&limit=10
Then the response status is 200
And the response contains both kind 1 events
Scenario: GET /api/events without required params is rejected
When the client sends GET /api/events without "since", "until", or "limit"
Then the response status is 400 Bad Request
# ---------------------------------------------------------------------------
# Fetching events (POST /api/events/filter — what most Nostr clients use)
# ---------------------------------------------------------------------------
Scenario: Client fetches recent notes (kind 1)
Given two kind 1 notes have been published
When the client sends POST /api/events/filter with body {"kinds": [1], "limit": 10}
Then the response status is 200
And the response contains both kind 1 events
And events are ordered newest first
Scenario: Client fetches a user profile (kind 0)
Given a kind 0 profile event has been published for keypair 1
When the client sends POST /api/events/filter with body {"kinds": [0], "authors": ["<pubkey1>"], "limit": 1}
Then the response status is 200
And the response contains exactly 1 event
And the event kind is 0
Scenario: Client fetches events mentioning a specific pubkey (#p tag)
Given an event tagged with ["p", "<some pubkey>"] has been published
When the client sends POST /api/events/filter with body {"#p": ["<some pubkey>"], "limit": 10}
Then the response status is 200
And the response contains the tagged event
Scenario: Client fetches events within a time window
Given two events with different created_at timestamps exist
When the client sends POST /api/events/filter with a "since" before the newer event
Then only the newer event is returned
Scenario: Filter without a limit is rejected
When the client sends POST /api/events/filter with body {"kinds": [1]}
Then the response status is 400 Bad Request
Scenario: Filter with a limit over 100 is rejected
When the client sends POST /api/events/filter with body {"limit": 101}
Then the response status is 400 Bad Request
# ---------------------------------------------------------------------------
# Fetching a single event by ID (GET /api/events/:id)
# ---------------------------------------------------------------------------
Scenario: Client fetches a specific event by ID
Given a known event has been published
When the client sends GET /api/events/<event_id>
Then the response status is 200
And the response body contains the correct event
Scenario: Fetching a non-existent event returns 404
When the client sends GET /api/events/<unknown_id>
Then the response status is 404 Not Found
# ---------------------------------------------------------------------------
# CORS — required for browser-based clients like Jumble
# ---------------------------------------------------------------------------
Scenario: Relay includes CORS headers on API responses
When the client sends any request to the API
Then the response includes "Access-Control-Allow-Origin: *"
And the response includes "Access-Control-Allow-Methods" listing GET, POST, DELETE, OPTIONS
Scenario: Relay handles a browser preflight OPTIONS request
When the client sends OPTIONS /api/events
Then the response status is 200
And the response includes CORS headers
And the response body is empty
# ---------------------------------------------------------------------------
# NIP-11 relay information document
# ---------------------------------------------------------------------------
Scenario: Client requests NIP-11 relay info
When the client sends GET / with header "Accept: application/nostr+json"
Then the response status is 200
And the response content-type is "application/nostr+json"
And the response includes a "name" field
And the response includes a "supported_nips" array
And the response includes a "limitation" object
And the response includes an "icon" field with a URL
Scenario: NIP-11 response includes correct CORS headers
When the client sends GET / with header "Accept: application/nostr+json"
Then the response includes "Access-Control-Allow-Origin: *"
Scenario: Regular browser request to GET / still serves the HTML page
When the client sends GET / with header "Accept: text/html"
Then the response status is 200
And the response content-type contains "text/html"
# ---------------------------------------------------------------------------
# Health check
# ---------------------------------------------------------------------------
Scenario: Health check returns OK
When the client sends GET /health
Then the response status is 200