Skip to content
⌘K
Marketplace API

Marketplace data

Real-time notifications

Stream per-wallet notices (sales, offers, coins, NFTs, airdrops, alerts) and live market activity over server-sent events, with resume.

The API pushes two kinds of real-time data over server-sent events (SSE):

StreamEndpointWhat it carries
Wallet noticesGET /notify/streamNotices for the addresses you subscribe: items sold or bought, offers received, coins and NFTs received, airdrops, perps liquidation warnings, price alerts
Live market dataGET /streamMarket activity, floor and listing changes, and network status, by topic

SSE is plain HTTP: one long GET that the server keeps open and writes frames to. It works with the browser's EventSource, with fetch in Node, with requests in Python and with curl -N.

Wallet notices

GET/notify/stream

Subscribing

ParameterNotes
addrComma list of addresses, up to 20 (extra ones are ignored). Any 0x form; the server normalises them.
deviceOptional device id for price alerts: 16 to 64 characters of A-Z a-z 0-9 _ -.
afterOptional notice id to resume after (see Resume).

Pass addr, device or both. With neither, the request fails with 400 bad_request ("Pass addr or device"). An invalid address or device id is also a 400.

No API key or session is needed, and CORS is open (Access-Control-Allow-Origin: *), so a web page on any origin can subscribe. Notices are built from public chain data, so any address can be watched. Opening a stream counts as one request toward your rate limit, and a client can hold 3 streams open at once per IP address, or 10 with an API key (pass it as x-api-key, or as ?apiKey= from an EventSource).

Notices are produced only for watched addresses. The first time an address is subscribed (on /notify/stream or /notify/recent), it is added to the watch list and notices start a few seconds later. Nothing is produced for events before that. An address stays watched for 30 days after its last subscription; an open stream keeps renewing it.

Frames

The stream starts with a reconnect hint and a hello event, then sends one notice event per notice. A comment line keeps the connection alive every 15 seconds.

Stream
retry: 3000

event: hello
data: {"now":1793067060773,"addrs":["0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543"]}

event: notice
data: {"id":"48213","kind":"sold","addr":"0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543","title":"Popkins #4104 sold for 72 SUI", ...}
id: 48213

: hb
FrameMeaning
retry: 3000Reconnect after 3 seconds if the connection drops. EventSource applies it automatically.
event: helloSent once per connection. data is { now, addrs }: the server time in epoch milliseconds and the normalised addresses you subscribed.
event: noticeOne notice. data is a Notice object (below). id is the notice id.
: hbHeartbeat comment every 15 seconds. Ignore it; if you see nothing at all for 45 seconds, reconnect.

Notice ids are numeric strings that only grow. Compare them as big integers, not as strings.

Notice object

notice
{
  "id": "48213",
  "kind": "sold",
  "addr": "0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543",
  "title": "Popkins #4104 sold for 72 SUI",
  "body": "Sold into an offer from 0x1e74...1ea2 on Tradeport",
  "imageUrl": "https://storage.claynosaurz.com/popkins/images/0x909c8f92d9556480466180c327f677fb69a9811ad8dd004d3b1c39bf3f09db5a",
  "amount": {
    "raw": "72000000000",
    "decimals": 9,
    "symbol": "SUI",
    "coinType": "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI",
    "usd": 65.6172
  },
  "item": {
    "id": "0x909c8f92d9556480466180c327f677fb69a9811ad8dd004d3b1c39bf3f09db5a",
    "name": "Popkins #4104",
    "imageUrl": "https://storage.claynosaurz.com/popkins/images/0x909c8f92d9556480466180c327f677fb69a9811ad8dd004d3b1c39bf3f09db5a",
    "collection": { "slug": "popkins", "name": "Popkins" }
  },
  "action": {
    "kind": "item",
    "itemId": "0x909c8f92d9556480466180c327f677fb69a9811ad8dd004d3b1c39bf3f09db5a",
    "slug": "popkins"
  },
  "digest": "DhTWmsU2jGPGVs2q8MhA1nQttgqw5Le6vQfH5CEkVhDR",
  "cpTs": 1793066412383,
  "createdAt": 1793066413105,
  "sentAt": 1793066413241
}
FieldTypeNotes
idstringNotice id. Use it for resume and to dedupe.
kindstringSee Notice kinds.
addrstring or nullThe subscribed address the notice is for. For a price alert, the address the alert was created with, or null.
titlestringReady to show, for example Popkins #4104 sold for 72 SUI.
bodystring or nullSecond line, for example Bought by 0x1e74...1ea2 on Tradeport.
imageUrlstring or nullItem image, coin logo or collection logo. Absolute URL.
amountobject or null{ raw, decimals, symbol, coinType, usd }. raw is the amount in base units as a decimal string; usd is the value in US dollars, or null when no price is known.
itemobject or null{ id, name, imageUrl, collection }. collection is { slug, name } or null. For a collection offer, item.id is "" and the name and image are the collection's.
actionobjectWhere the notice points. See Actions.
digeststring or nullTransaction digest, when the notice comes from a transaction.
cpTsnumber or nullCheckpoint time in epoch milliseconds.
createdAtnumberWhen the notice was recorded, epoch milliseconds.
sentAtnumberWhen this copy was sent to you, epoch milliseconds.

Unlike the rest of the API, notice times are epoch milliseconds (numbers), not ISO strings.

Notice kinds

kindWhenExample titleExample body
soldAn item you held sold, from your listing or into an offer you acceptedPopkins #4104 sold for 72 SUIBought by 0x1e74...1ea2 on Tradeport or Sold into an offer from 0x1e74...1ea2 on Tradeport
boughtYou bought an item, or someone accepted your offerYou bought Popkins #4104 for 72 SUI or Your offer on Popkins #4104 was acceptedFrom 0x2b9c...07fe on Tradeport or 72 SUI paid to 0x2b9c...07fe on Tradeport
offer_receivedA new offer on an item you hold, or a new top collection offer on a collection you holdNew offer on Popkins #4104: 72 SUI or New collection offer on Popkins: 49 SUIFrom 0x1e74...1ea2 on Tradeport
coin_receivedSomeone else sent you coins12.5 SUI received or 250 USDC received ($249.98)From 0x2b9c...07fe
nft_receivedSomeone else sent you an item of a collection rare.market listsPopkins #4104 receivedFrom 0x2b9c...07fe
airdropAn item of a listed collection was created straight into your walletAirdrop landed: Popkins #4104From 0x2b9c...07fe
perp_liquidationAn open perps position of the address is close to its liquidation priceSUI long is near liquidationMark $0.91, liquidation $0.87, 4.4% away
price_alertA price alert you set firedDEEP is above $0.25Now $0.251

Venue names in body are Rare, Tradeport, Drip and Sui Kiosk. Addresses are shortened to 0x plus 4 characters, ... and the last 4.

What is filtered out, so you do not have to:

  • Coins: only coins sent by another address, summed per coin per transaction. Amounts under 0.1 SUI, or under $1 for other coins, are dropped, and so are spam coins and coins with no known price. Coins that are part of a trade you already get a sold or bought notice for are not repeated.
  • Collection offers: only when the new offer is the highest open collection offer on that collection, and at most one notice per collection per 15 minutes. Bidding bots outbid each other every few seconds.
  • Your own transactions: offers you place, and coins or items that arrive in a transaction you sent yourself, do not notify you.
  • One per event: when one event concerns several of the addresses on a connection (a collection offer on a collection two of your wallets hold), that connection gets it once.
  • Perps: a position notifies when it gets within 10% of its liquidation price, again within 5%, and re-arms once it moves more than 15% away. Positions are checked every 15 seconds, and only for addresses with an open stream.

Actions

action.kind tells you where to send the user:

actionShapeSuggested target
item{ kind, itemId, slug }https://rare.market/item/{itemId}
collection{ kind, slug }https://rare.market/collection/{slug}
tx{ kind, digest }A Sui explorer page for the transaction
coin{ kind, coinType }The coin's page in your app
position{ kind, marketId, symbol }The perps position in your app

Resume

Every notice frame has an id: line. To pick up where you left off after a disconnect, send the last id you processed:

  • the Last-Event-ID request header, or
  • the after query parameter (?after=48213).

When both are present, the header wins. On connect, the server first sends every notice newer than that id from the last 7 days (at most 200, oldest first), then continues live. Notices are kept for 14 days; read older ones with /notify/recent.

The browser's EventSource sends Last-Event-ID by itself when it reconnects, but it forgets the id when the page reloads. Store the last id yourself and pass it as after on a fresh page load.

Dedupe by id on the client. A notice that arrives live while the backlog is being read can show up near it, and /notify/recent overlaps with the stream.

Reconnecting

  • EventSource reconnects automatically after the 3 second retry hint and resumes with Last-Event-ID. You do not need to do anything.
  • With fetch or requests, reconnect yourself: wait, send Last-Event-ID, and back off (for example 3, 6, 12, up to 30 seconds) while the server is unreachable.
  • Treat 45 seconds without any bytes (heartbeats included) as a dead connection.
  • A 400 will not succeed on retry. Fix the request instead.
  • A slow reader that lets more than 500 frames queue up on one connection loses the newer ones. Reconnect with Last-Event-ID to fetch them.

Examples

Browser (EventSource)

TypeScript (browser)
const API = "https://rare.market/api/v1";
const addrs = ["0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543"];
const LAST_KEY = `rare.notify.last.${addrs.join(",")}`;

type Notice = {
  id: string;
  kind: "sold" | "bought" | "offer_received" | "coin_received" | "nft_received" | "airdrop" | "perp_liquidation" | "price_alert";
  addr: string | null;
  title: string;
  body: string | null;
  imageUrl: string | null;
  amount: { raw: string; decimals: number; symbol: string; coinType: string; usd: number | null } | null;
  item: { id: string; name: string; imageUrl: string | null; collection: { slug: string; name: string } | null } | null;
  action:
    | { kind: "item"; itemId: string; slug: string | null }
    | { kind: "collection"; slug: string }
    | { kind: "position"; marketId: string; symbol: string }
    | { kind: "coin"; coinType: string }
    | { kind: "tx"; digest: string };
  digest: string | null;
  cpTs: number | null;
  createdAt: number;
  sentAt: number;
};

function readLast(): string | null {
  try {
    return localStorage.getItem(LAST_KEY);
  } catch {
    return null;
  }
}
function saveLast(id: string) {
  try {
    localStorage.setItem(LAST_KEY, id);
  } catch {
    /* storage unavailable: resume only within this page */
  }
}

const seen = new Set<string>();
function show(n: Notice) {
  if (seen.has(n.id)) return;
  seen.add(n.id);
  console.log(`[${n.kind}] ${n.title}${n.body ? ` (${n.body})` : ""}`);
}

const url = new URL(`${API}/notify/stream`);
url.searchParams.set("addr", addrs.join(","));
const last = readLast();
if (last) url.searchParams.set("after", last); // fresh page load: resume from storage

const es = new EventSource(url);

es.addEventListener("hello", (e) => {
  const hello = JSON.parse((e as MessageEvent<string>).data) as { now: number; addrs: string[] };
  console.log("subscribed", hello.addrs);
});

es.addEventListener("notice", (e) => {
  const msg = e as MessageEvent<string>;
  show(JSON.parse(msg.data) as Notice);
  if (msg.lastEventId) saveLast(msg.lastEventId);
});

es.onerror = () => {
  // EventSource retries on its own after 3 s and sends Last-Event-ID.
  // readyState CLOSED means it gave up (for example a 400): fix the request.
  if (es.readyState === EventSource.CLOSED) console.error("notify stream closed");
};

// Later: es.close();

Node.js (fetch)

Node 18 or later, no dependencies. The sse helper is a complete SSE client (parser, heartbeat watchdog, resume and backoff) and also works for /stream.

TypeScript (Node)
const API = "https://rare.market/api/v1";

type Frame = { event: string; data: string; id?: string };

/** Connects to an SSE endpoint and keeps it connected. Resolves only when `signal` aborts. */
async function sse(
  url: URL,
  onFrame: (f: Frame) => void,
  opts: { lastEventId?: string; signal?: AbortSignal } = {},
): Promise<void> {
  let lastId = opts.lastEventId ?? null;
  let retryMs = 3000;
  let failures = 0;

  while (!opts.signal?.aborted) {
    const ctrl = new AbortController();
    const onStop = () => ctrl.abort();
    opts.signal?.addEventListener("abort", onStop);
    let watchdog = setTimeout(() => ctrl.abort(), 45_000);
    try {
      const headers: Record<string, string> = { accept: "text/event-stream" };
      if (lastId) headers["last-event-id"] = lastId;
      const res = await fetch(url, { headers, signal: ctrl.signal });
      if (res.status === 400) throw Object.assign(new Error(await res.text()), { fatal: true });
      if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
      failures = 0;

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buf = "";
      let event = "message";
      let data: string[] = [];
      let id: string | undefined;

      for (;;) {
        const { value, done } = await reader.read();
        if (done) break;
        clearTimeout(watchdog);
        watchdog = setTimeout(() => ctrl.abort(), 45_000);
        buf += decoder.decode(value, { stream: true });

        let nl: number;
        while ((nl = buf.indexOf("\n")) >= 0) {
          let line = buf.slice(0, nl);
          buf = buf.slice(nl + 1);
          if (line.endsWith("\r")) line = line.slice(0, -1);

          if (line === "") {
            // Blank line: dispatch the frame.
            if (data.length) {
              if (id) lastId = id;
              onFrame({ event, data: data.join("\n"), id });
            }
            event = "message";
            data = [];
            id = undefined;
            continue;
          }
          if (line.startsWith(":")) continue; // heartbeat comment

          const i = line.indexOf(":");
          const field = i < 0 ? line : line.slice(0, i);
          let val = i < 0 ? "" : line.slice(i + 1);
          if (val.startsWith(" ")) val = val.slice(1);
          if (field === "event") event = val;
          else if (field === "data") data.push(val);
          else if (field === "id") id = val;
          else if (field === "retry" && /^\d+$/.test(val)) retryMs = Number(val);
        }
      }
    } catch (e) {
      if ((e as { fatal?: boolean }).fatal) throw e;
      if (!opts.signal?.aborted) {
        failures += 1;
        console.error("stream dropped:", (e as Error).message);
      }
    } finally {
      clearTimeout(watchdog);
      opts.signal?.removeEventListener("abort", onStop);
    }
    if (opts.signal?.aborted) return;
    const wait = Math.min(30_000, retryMs * 2 ** Math.min(failures, 4));
    await new Promise((r) => setTimeout(r, wait));
  }
}

// ---------- wallet notices ----------

const addrs = ["0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543"];
const url = new URL(`${API}/notify/stream`);
url.searchParams.set("addr", addrs.join(","));

const seen = new Set<string>();
await sse(url, (f) => {
  if (f.event === "hello") {
    console.log("subscribed", JSON.parse(f.data).addrs);
    return;
  }
  if (f.event !== "notice") return;
  const n = JSON.parse(f.data) as { id: string; kind: string; title: string; body: string | null };
  if (seen.has(n.id)) return;
  seen.add(n.id);
  console.log(`[${n.kind}] ${n.title}${n.body ? ` (${n.body})` : ""}`);
  // Persist f.id here and pass it back as { lastEventId } after a restart.
});

Save it as notify.mts and run node notify.mts (Node 23.6 or later runs TypeScript directly; on older versions use npx tsx notify.mts).

Python (requests)

Python
import json
import time

import requests

API = "https://rare.market/api/v1"


def sse(url, params, on_frame, last_event_id=None):
    """Connects to an SSE endpoint and keeps it connected. Calls on_frame(event, data, id)."""
    retry = 3.0
    failures = 0
    while True:
        headers = {"Accept": "text/event-stream"}
        if last_event_id:
            headers["Last-Event-ID"] = last_event_id
        try:
            # Read timeout 45 s: heartbeats arrive every 15 s, so silence means a dead link.
            with requests.get(url, params=params, headers=headers, stream=True, timeout=(10, 45)) as resp:
                if resp.status_code == 400:
                    raise SystemExit(resp.json()["error"]["message"])
                resp.raise_for_status()
                resp.encoding = "utf-8"
                failures = 0
                event, data, frame_id = "message", [], None
                # chunk_size=None hands over bytes as they arrive (no 512-byte buffering).
                for line in resp.iter_lines(chunk_size=None, decode_unicode=True):
                    if line == "":
                        if data:
                            if frame_id:
                                last_event_id = frame_id
                            on_frame(event, "\n".join(data), frame_id)
                        event, data, frame_id = "message", [], None
                        continue
                    if line.startswith(":"):
                        continue  # heartbeat comment
                    field, _, value = line.partition(":")
                    if value.startswith(" "):
                        value = value[1:]
                    if field == "event":
                        event = value
                    elif field == "data":
                        data.append(value)
                    elif field == "id":
                        frame_id = value
                    elif field == "retry" and value.isdigit():
                        retry = int(value) / 1000
        except requests.RequestException as e:
            failures += 1
            print("stream dropped:", e)
        time.sleep(min(30, retry * 2 ** min(failures, 4)))


# ---------- wallet notices ----------

addrs = ["0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543"]
seen = set()


def on_frame(event, data, frame_id):
    if event == "hello":
        print("subscribed", json.loads(data)["addrs"])
    elif event == "notice":
        n = json.loads(data)
        if n["id"] in seen:
            return
        seen.add(n["id"])
        print(f"[{n['kind']}] {n['title']}" + (f" ({n['body']})" if n["body"] else ""))
        # Persist frame_id here and pass it back as last_event_id after a restart.


sse(f"{API}/notify/stream", {"addr": ",".join(addrs)}, on_frame)

curl

curl
# Live notices for two wallets (-N turns off buffering)
curl -N "https://rare.market/api/v1/notify/stream?addr=0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543,0x1e74849ee17df6746ca3911cfbfe7c80e4d2e85d4395c0ca06c2f56d8f4d1ea2"

# Resume after notice 48213
curl -N -H "Last-Event-ID: 48213" \
  "https://rare.market/api/v1/notify/stream?addr=0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543"

Recent notices

GET/notify/recent

The newest notices for the same addr and device parameters, as JSON. Use it to fill a notification list on load, then keep it current with the stream.

ParameterNotes
addr, deviceAs for the stream. Calling this also adds the addresses to the watch list.
limit1 to 100. Default 50.
beforeA notice id: return notices older than it. Use the previous response's nextBefore.
curl "https://rare.market/api/v1/notify/recent?addr=0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543&limit=20"
Response
{
  "data": [
    {
      "id": "48213",
      "kind": "coin_received",
      "addr": "0x16386f6d07a85631e419e8f83b1d99a13949515a6af7ba3f81f3cfb0ec7e5543",
      "title": "12.5 SUI received",
      "body": "From 0x2b9c...07fe",
      "imageUrl": "https://rare.market/sui.svg",
      "amount": {
        "raw": "12500000000",
        "decimals": 9,
        "symbol": "SUI",
        "coinType": "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI",
        "usd": 11.39
      },
      "item": null,
      "action": { "kind": "tx", "digest": "HtxpgowKkK6hubtatrP2Qmmcu7dXDvaWGBHq9yRPw3nj" },
      "digest": "HtxpgowKkK6hubtatrP2Qmmcu7dXDvaWGBHq9yRPw3nj",
      "cpTs": 1793066448622,
      "createdAt": 1793066449310,
      "sentAt": 1793067060912
    }
  ],
  "nextBefore": null
}

data is newest first. nextBefore is set when the page was full; pass it as before for the next page. It is null at the end.

Price alerts

Price alerts belong to a device id, not to a wallet. Generate a random id once per install (16 to 64 characters of A-Z a-z 0-9 _ -), keep it, and pass it as device on the stream to receive the price_alert notices. Anyone who knows the device id can read and delete its alerts, so treat it like a secret.

EndpointBody or queryReturns
POST /notify/alertsJSON { device, coinType, dir, target, addr? }{ id }
GET /notify/alerts?device={ data: [{ id, coinType, symbol, dir, target, createdAt, firedAt, active }] }, newest first, up to 100. Times are epoch milliseconds.
DELETE /notify/alerts/{id}?device={ ok: true }

dir is above or below; target is a price in US dollars. coinType is a full coin type such as 0x2::sui::SUI. addr is optional and is echoed as the notice's addr. Prices are checked every 15 seconds. An alert fires once (active becomes false, firedAt is set), and a device can have up to 50 active alerts. Errors are 400 bad_request with messages such as dir must be above or below, No price for this coin and Up to 50 active alerts.

curl -X POST "https://rare.market/api/v1/notify/alerts" \
  -H "content-type: application/json" \
  -d '{"device":"k3T9xQ2mV7pL0aZ4wR8yB1nC","coinType":"0x2::sui::SUI","dir":"above","target":1.25}'

curl -N "https://rare.market/api/v1/notify/stream?device=k3T9xQ2mV7pL0aZ4wR8yB1nC"

Delivery health

GET/notify/health
Response
{
  "listening": true,
  "subscribers": 5,
  "owners": 56,
  "lastNoticeLagMs": 1038,
  "p50LagMs": 1119,
  "p95LagMs": 2767,
  "samples": 221
}

subscribers is open stream connections and owners the distinct addresses and devices they cover. The lag figures are the time from a notice's checkpoint to the moment it was written to a stream, over the last 500 notices.

Public live stream

GET/stream

The market-wide stream that powers live grids and activity panels. Pass up to 50 comma-separated topics:

TopicEvents
activityactivity for every market event (the global feed)
collection:{slug}activity for the collection, plus floor and listing
account:{address}activity where the address is the from or to side. Use the full 64-character lowercase address.
statusstatus on connect and every 5 seconds
Eventdataid
activityAn ActivityEvent, the same object as GET /eventsThe event's chain position checkpoint:txSeq:eventIndex
floor{ slug, floorMist, listedCount }: the new floor (lowest all-in listing, or null) and listing countnone
listing{ slug, itemId, listing }: the item's current cheapest Listing, or null once it is no longer listednone
status{ suiUsd, tps, checkpoint, degraded }none

New events are picked up every 2 seconds. floor and listing follow any activity that can change listings (sales, lists, delists, transfers, mints), not offers. A comment line : heartbeat is sent every 15 seconds.

Resume works with the Last-Event-ID header only (no query parameter), and only for activity: send the last activity id and the server replays the matching events of up to the last hour (at most 200) before going live. This stream sends no retry line, so EventSource uses the browser's default reconnect delay.

curl
curl -N "https://rare.market/api/v1/stream?topics=collection:popkins,status"
Stream
event: status
data: {"suiUsd":0.9123,"tps":122,"checkpoint":338534372,"degraded":false}

event: activity
data: {"id":"338534515:6357925207:0","kind":"offer_cancel","venue":"tradeport","item":{"id":"","name":"Mystic Yetis","imageUrl":"https://indexer.mypinata.cloud/ipfs/bafkreid6owvnhp2v2muf4yrk4v3hjonhposfgsnd5zirfzgbyutxoasxay","rarity":null},"collection":{"slug":"mystic-yetis","name":"Mystic Yetis","imageUrl":"https://indexer.mypinata.cloud/ipfs/bafkreid6owvnhp2v2muf4yrk4v3hjonhposfgsnd5zirfzgbyutxoasxay","verified":true},"priceMist":"15200100000","from":"0xa7b32af76de025a0080904a498071d64548581f15b20be3e1ec052dc4908cc13","to":null,"fromName":null,"toName":null,"txDigest":"DKU3QgyZ2R52o6wHkqh8PWbhSjPXZMkR1juhYg757x8k","at":"2026-10-27T02:10:47.419Z"}
id: 338534515:6357925207:0

: heartbeat

With the sse helpers above:

curl -N -H "Last-Event-ID: 338534515:6357925207:0" \
  "https://rare.market/api/v1/stream?topics=collection:popkins"

/stream is rare.market's own live feed: it is not in the API reference, sends no CORS headers, and can change without notice. Open streams count toward the same per-client cap as /notify/stream. For a stable feed in your app, use /notify/stream, or poll GET /events with caching.

--
TPS:--