raresats.net API
Site feeds View as Markdown

Live events

Everything the site shows changes live. One Server-Sent Events stream, GET /api/events, carries:

  • each new block;
  • the sales;
  • every listing and offer change;
  • the floors;
  • the marketplace's activity;
  • affiliate stats;
  • the wallet transfers the site broadcast;
  • the BTC/USD price;
  • the chat.

A page opens one stream and updates in place. A script can do the same with any SSE client.

GET/api/events

Server-Sent Events: a snapshot (the state) on connect, then status, backfill, block, supply, reorg, snapshot and sales events, mempool (an invalidation whenever what is pending changes: {version, keys: {sales, transfers, extractions, rows, addresses, outpoints}, counts: {sales, transfers, extractions}}; read /api/pending, or the answer you show, again when a key you care about is set), the chat's chat ({type: "message", topic, message, thread?}, a reply with its root's new counts in thread, {type: "delete", ids, threads, topics}, threads the roots whose replies went, or {type: "topic", topic, archived}) and profile ({address, display_name}) events while chat is on, the marketplace's listing, offer, market, activity, affiliate and transfer (a wallet transfer broadcast here changed status: {version, count, keys: {addresses}, changes}) events while it is on, and a ping every 15 s. Limited to 2,048 open streams, 8 from one IPv4 address or IPv6 /64 and 32 from one IPv6 /48. When every place is taken, a newcomer takes the place of the oldest stream of the network holding the most (at least two more than the newcomer's): that stream gets retry: 60000 and an evicted event ({reason, retry_ms}), then ends; reconnect after retry_ms with last_event_id.

Query parameters #

ParameterTypeRequiredDescription
limitintegernoEvents per list in the snapshot, 1-1000. Default: 500. (min 1, max 1000)
last_event_idintegernoResume after this event id (what Last-Event-ID says; for a client that reopens the stream itself). Unknown or too old: a snapshot instead. (min 0)

Example request #

cURL
curl 'https://raresats.net/api/events'
JavaScript
const response = await fetch('https://raresats.net/api/events');
const data = await response.json();
Python
import requests

data = requests.get("https://raresats.net/api/events").json()

Response 200 #

An event stream.

Content type: text/event-stream.

Errors #

{error} (a message, no code):

StatusWhen
503Too many open streams from your network (8 from an IPv4 address or IPv6 /64, 32 from an IPv6 /48), or every place taken and no network holding more than yours.

Frames #

Each frame has three fields, and data is one line of JSON:

id: 1790950000000042
event: listing
data: {"version":7,"changes":[…],"listings":[…]}
  • Ids. Ids increase strictly, across server restarts too. ping, hello and the retry: line carry none, so Last-Event-ID always names a real event.
  • Ping. A ping ({"time": <ms>}) comes every 15 s. A stream silent for longer than about 45 s is dead: open it again.
  • Hello. hello is the first frame of every connection: {"boot": <ms>, "resumed": <bool>, "replayed": <n>, "last_id": <id>}.

Resuming #

Send the id of the last event you handled, either in the Last-Event-ID header (browsers do this on their own reconnects) or as ?last_event_id=.

  • Replayed. If the server still holds every event after it, it sends them in order (hello.resumed: true) and then goes on live. No snapshot is sent.
  • Not replayed. If the server no longer holds them, it sends a snapshot (hello.resumed: false). The id may be from before a restart, or too old for the server's buffer of the last 2,000 events or 4 MiB. Treat this as a gap: read again whatever you show.

A snapshot frame's id is the newest event's id when it was sent, so resuming from it replays only what came after.

  • Taken down. The replay never brings back what a chat admin removed. A deleted message's frame is dropped (and a quote of it emptied); a removed or changed display name drops the address's earlier profile frames and is written into its earlier messages. The removals themselves (chat delete, profile) are replayed in their place.

Limits #

A server holds at most 2,048 streams: 8 from one IPv4 address or IPv6 /64, and 32 from one IPv6 /48. Over your network's share, the stream is refused with a 503. When every place is taken, a newcomer takes the place of the oldest stream of the network holding the most (if it holds at least two more than the newcomer's). That stream gets retry: 60000 and an evicted event, {"reason", "retry_ms"}, then ends: reconnect after retry_ms, resuming from your last id.

Event types #

EventWhenData
hellofirst frame of a connection{boot, resumed, replayed, last_id}
snapshoton connect when not resumed; after the server rebuilds its windowwhat GET /api/state answers
statusthe index's reachability, mode or tip changedwhat GET /api/status answers
backfillthe server reads past blocks{done, total, …}
blocknew blocks read{tip, status, supply, transfers, extractions}: the new notable transfers and extractions only
supplythe supply was read at a new tip{supply, status}
reorgblocks were replaced{from_height, common_height, removed: {transfers, extractions}, tip}: the ids of the items dropped
salesthe sales changed{available, generation, seq, count, as_of, first_time, stats}. Read the new sales with GET /api/sales?after=<seq> while generation is the one you hold; on a new generation read them all again
listinglistings changed statesee Listing events
offeroffers changed statesee Offer events
marketa row's floor or listed count changed{version, floors: {<row>: {floor_unit, listed, listed_sats, contains?, contains_min_price?} | null}}: only the rows that changed, as floors gives them; null means nothing is listed there now, nor in a bundle
activitynew marketplace activity{version, events: [ActivityEvent] | null, keys: {rows, addresses}}: events as GET /api/market/activity lists them, oldest first; null over 25
affiliatean affiliate registered (optional), or a purchase it referred was made or settled{version, keys: {addresses}}: read GET /api/market/affiliates/{address} again
transfera wallet transfer the site broadcast went pending, confirmed, was replaced (sped up, cancelled), conflicted, dropped or was refused (rejected)see Transfer events
pricethe BTC/USD price changed (read every 5 minutes while a page is open){current: {usd, time}}
chata message or reply was posted, or messages were deleted{type: "message", message, thread?} or {type: "delete", ids, threads}
profilea display name changed{address, display_name}
pingevery 15 s{time}
evictedthe stream is ended to make room (see Limits){reason, retry_ms}

Listing events #

{"version": 7, "count": 1,
 "keys": {"rows": [rows], "sellers": [addresses]},
 "changes": [{"id", "outpoint", "status", "row", "under": [rows], "seller_address", "price", "unit_price", "updated_at"}] | null,
 "listings": [Listing] | null}
  • What a change says. Each change gives the listing's new status: active, pending, sold, cancelled or moved.
    • under lists every row the listing is shown under: its own, the rarer sub-categories', the label rows' and those it holds some sats of in a bundle. Use it to tell whether a list you show is affected.
  • Full objects. listings holds the full Listing objects, exactly as GET /api/market/listings/{id} answers.
  • Large bursts. A block that settles many listings, or a large batch listed, can change many at once. One event keeps its size down in two steps:
    • Over 25 listings: listings is null, and the changes are all still there.
    • Over 100: changes is null too, and keys (every row and seller touched) and count remain.

In either case, read again once the lists you show that the keys name. - Transitions. Every transition is a change, whichever route, block or mempool check made it: - Listed or repriced. A listing is created, or a new one replaces the old one: the old id is cancelled and the new one active. - Cancelled. - Bought. The listing goes pending with its txid, then sold once confirmed. If the purchase leaves the mempool unconfirmed it goes back to active. - Moved (moved). Its UTXO was spent without paying the listing. - Signature public. The listing's signature became public. - Placed under a new row.

Offer events #

{"version": 8, "count": 1,
 "keys": {"rows": [rows], "addresses": [sellers and bidders]},
 "changes": [{"id", "outpoint", "status", "row", "seller_address", "bidder_payment_address", "bidder_ordinals_address", "price", "updated_at"}] | null,
 "offers": [Offer] | null,
 "best": {"<outpoint>": Offer | null} | null}
  • Statuses. Each change gives the offer's new status: active, pending, accepted, cancelled, expired or invalid. The full Offer gives an invalid offer's reason.
  • best. best holds the best active offer of each outpoint the changes touched, as GET /api/market/offers/best answers it, or null when none is left. It is what a listing's best_offer shows.
  • Large bursts. As for listings: over 25 changes offers is null, and over 100 changes is null too. best is left out (null) above 200 outpoints.

An accepted offer also moves the UTXO's listing: a listing event says it is pending, then sold with sold_via: "offer".

Transfer events #

{"version": 9, "count": 1,
 "keys": {"addresses": [addresses]},
 "changes": [{"id", "txid", "kind", "status", "updated_at", "addresses": [addresses]}] | null}
  • When. A transfer built on the site (extract, send, consolidate, speed up, cancel, …) is broadcast (pending), and then settles: confirmed in a block, replaced by its speed-up or cancel, conflicted (spent by another transaction), dropped (left the mempool), unknown (the broadcast's answer was lost) or rejected (bitcoind refused it: never sent).
  • addresses. Every address whose page shows it: the owner's payment and ordinals addresses and every address it pays, the marketplace's fee address left out. Read GET /api/market/transfers?address=<a> again for an address you show.
  • Large bursts. Over 100 changes, changes is null and keys remains.

Transactions that move tracked sats also show in the mempool event while pending; this one covers every transfer the site broadcast, plain bitcoin too.

What never goes down the stream #

Events carry only what the GET endpoints already answer, from the same public forms. They never include:

  • a signature;
  • a PSBT;
  • an offer's plan;
  • a script;
  • a purchase's reservation.

Bursts and load #

  • Coalescing. The server gathers a market burst for 250 ms and sends one event of each type.
  • Reading again. A client should do the same: wait a moment after an event and read once what it shows. Never send one request per change.
  • Hidden pages. A hidden page may skip its reads and read once when it shows again.