raresats.net API
Market data View as Markdown

Sales

GET/api/sales

Every sale of tracked sats the index found, on every marketplace, oldest first, as compact columns (gzip when accepted). Follow new sales with after=<seq>. Addresses are left out: ask /api/sales/parties. Cacheable 30 s. With years, month or luck, only the sales that pass (the filter echoed as filter); 501 on a server without the filters, 503 (retry: true) while the mint days or the luck are still being read, 429 (retry: true, with Retry-After) past your network's budget of new filtered answers (a cached one is always served). Sales still in the mempool come apart, in pending (newest first, marked pending: true with first_seen and fee_rate); they never enter the columns, stats or markets.

Query parameters #

ParameterTypeRequiredDescription
rowstringnoOnly sales of this row.
groupstringnoOnly sales of this group's rows (e.g. rarity). Ignored with row.
afterintegernoOnly sales after this sequence number (an answer's seq). A cursor at most 5,000 sales behind the newest is answered as asked; one further back is answered whole (after: 0 in the answer: replace what you hold rather than append), and one past the newest as the newest. Default: 0. (min 0)
substringnoWith row: 1 adds its rarer sub-categories' sales, flagged 4. One of: 0, 1. Default: 0.
ledstringnoWith row or group: 1 adds the sales led by another row that sold sats of these rows (their secondary rows), flagged 8, outside every market and median. One of: 0, 1. Default: 0.
yearsstringnoOnly the sales of the row's sats mined in these years (comma-separated YYYY; year too): a sale passes when a sat of the row (of any row, without row) it sold was mined in one of them, as /api/sales/query and the collection's Activity match it. The Statistics' and a row detail's sales under a year filter. Years 2009 to this one (400 otherwise).
monthstringnoOnly the sales of each row's sats mined in this month (UTC, YYYY-MM, by each block's own time), matched on the dashboard: any rare-sat index.
blocksstringnoOnly the sales of each row's sats mined in these blocks: heights from-to, inclusive (1000-5000), or one block (9), up to the tip (400 past it or for a malformed or reversed range), matched on the dashboard: any rare-sat index.
luckstringnoOnly the sales of the row's sats mined in blocks of these lucks: N (N or more) or N-M, matched as years is. With years, both.

Example request #

cURL
curl 'https://raresats.net/api/sales?row=uncommon&years=2009&month=2017-12&blocks=1000-5000&luck=10'
JavaScript
const response = await fetch('https://raresats.net/api/sales?row=uncommon&years=2009&month=2017-12&blocks=1000-5000&luck=10');
const data = await response.json();
Python
import requests

data = requests.get("https://raresats.net/api/sales", params={"row": "uncommon", "years": "2009", "month": "2017-12", "blocks": "1000-5000", "luck": "10"}).json()

Response 200 #

FieldTypeDescription
availablebooleanWhether the sales index is available.
generationintegerSales generation: a reorg starts a new one; reload from after=0.
seqintegerThe newest sale's sequence number: pass it as after to get only newer sales.
afterintegerThe after asked for.
countintegerSales in this answer (the length of every column).
as_ofobject | null{height, time} of the sales.
rowsarray of stringRow ids, indexed by r, o and w.
marketsarray of stringMarketplace ids, indexed by m.
sub_countintegerWith one market row: sales of its rarer sub-categories.
sub_rowsmap of integerThose sales by their own row.
led_countintegerWith one market row: sales led by another row that sold sats of it (it is one of their secondary rows).
led_rowsmap of integerThose sales by their own row.
tarray of integerSale time, unix seconds, as deltas: the first from 0, each next from the one before.
qarray of integerSequence number, as deltas: the first from after.
uarray of number | nullUnit price under the row shown.
parray of integer | nullPrice in sats.
carray of integerSats of the row sold.
rarray of integerIndex into rows: the row the sale is shown as.
marray of integerIndex into markets.
farray of integerFlags: 1 inscribed, 2 outlier, 4 a rarer sub-category's sale (with sub=1), 8 a sale led by another row, shown under one of its secondary rows (with led=1), 16 the sale has secondary rows (an item in w).
mdarray of number | nullThe row's rolling median unit price at the sale (4 significant digits).
oarray of integer | nullWith sub=1 or led=1: index into rows of a sub-category's or a led sale's own row, else null.
sarray of integer | nullThe first special sat sold.
sharray of integer | nullThe block that mined it.
sdarray of integer | nullThe day it was mined (UTC days since 1970).
narray of integerTracked sats sold.
xarray of string | nullThe txid's first 8 and last 4 hex digits (/api/sales/{seq}/tx redirects to the full one).
warray of array of integerOne item per sale flagged 16, in their order: its secondary rows, primary-first, as [row index, sats, row index, sats, …] (indexes into rows).
karray of integerThe exact types each sale sold: an index into kinds.
kindsarray of array of integerThe kinds of sale k names, each [type set index, sats, type set index, sats, …] (indexes into type_sets): the sats it sold grouped by exact type combination, its own row's combination first, then rarest first. A piece longer than one sat (a block 9 chunk) counts under its first sat's types. [] when the fork typed none.
type_setsarray of array of stringThe exact type combinations kinds names, each in the badges' order (["uncommon", "alpha"]).
errorstring | nullThe sales index's last error, if any.
pendingarray of objectThe selection's sales still in the mempool, newest first (the same row, group, sub and led rules). Never in the columns, count, seq or any market: they confirm as new sales.
pending[].seqnullNo sequence number until it confirms.
pending[].tinteger | nullfirst_seen: sorted by time, it leads the confirmed sales.
pending[].hnullNo block yet.
pending[].rowstringThe sale's own row.
pending[].unumber | nullUnit price: premium per sat of the row.
pending[].cintegerSats of the row sold.
pending[].pinteger | nullPrice in sats.
pending[].nintegerTracked sats the sale sold.
pending[].sinteger | nullThe first special sat sold.
pending[].shinteger | nullThe block that mined it.
pending[].sdinteger | nullThe day it was mined (UTC days since 1970).
pending[].mstringMarketplace id (magisat, magic_eden, unknown, …).
pending[].xstring | nullThe txid's first 8 and last 4 hex digits.
pending[].insbooleanAn inscription was on a sold sat.
pending[].outbooleanAlways false: a pending sale is judged by no median.
pending[].secondarymap of integerA sale's secondary rows: the market rows of the other sats it sold (not its own row, a row it is a sub-category of, or a label row), each with its sats, primary-first: {"black_omega": 1} for a black omega sold with an alpha uncommon; {} for none. A sale never counts in its secondary rows' markets (the primary-sat rule, in Concepts).
pending[].sellerstring | nullThe seller's address.
pending[].buyerstring | nullThe buyer's address.
pending[].keystringtxid:vin: its identity. The confirmed sale has the same txid and vin and replaces it.
pending[].pendingtrueAlways true: the sale is still in the mempool.
pending[].first_seeninteger | nullWhen the transaction was first seen in the mempool (bitcoind's entry time), unix seconds.
pending[].fee_ratenumber | nullIts fee rate, sat/vB (one decimal).
pending[].txidstringThe sale transaction, whole.
pending[].vininteger | nullThe seller's input.
pending[].seller_outpointstring | nullThe output it sells.
pending[].buyer_outpointstring | nullThe output the sold sats go to.
pending[].rowsarray of stringIts own row, then the rows of which it is a rarer sub-category.
pending[].led_rowsarray of stringThe rows of its other sats (it is led by row there).
pending[].labelsarray of stringThe label rows it lies in.
pending[].kindstring | nullThe fork's sale kind (listing, offer, …).

Errors #

{error} (a message, no code):

StatusWhen
400A malformed after, sub, years or luck.
404An unknown row or group. A retired row (uncommon_2009, rare_2009) answers 404 too, its retired field naming the row and years to ask for instead.
429Too many new filtered answers (each a pass over every sale) from your network (an IPv4 address or IPv6 /64), or from everyone at once: retry: true; try again after Retry-After seconds. Answers already built are served all the same.
501years or luck asked of a server without the sat filters.
503The mint days or the block luck are still being read (retry: true), or the luck is not available.

Reading the columns #

The answer is column-oriented: entry i of every column is sale i, oldest first. Decode it like this:

Decode
const data = await (await fetch('https://raresats.net/api/sales?row=uncommon')).json();
let time = 0;
let seq = data.after;
let w = 0; // the next item of `w`
const pairs = list => Object.fromEntries(list.flatMap((v, j) => (j % 2 ? [] : [[data.rows[v], list[j + 1]]])));
const kind = list => list.flatMap((v, j) => (j % 2 ? [] : [{ types: data.type_sets[v], sats: list[j + 1] }]));
const sales = data.t.map((dt, i) => {
  time += dt;
  seq += data.q[i];
  return {
    seq,
    time,                               // unix seconds
    row: data.rows[data.r[i]],          // the row it is shown as
    unit_price: data.u[i],              // premium per sat of that row
    price: data.p[i],                   // sats
    count: data.c[i],                   // sats of the row sold
    marketplace: data.markets[data.m[i]],
    inscribed: (data.f[i] & 1) !== 0,
    outlier: (data.f[i] & 2) !== 0,
    sub_category: (data.f[i] & 4) !== 0,
    led: (data.f[i] & 8) !== 0,         // led by another row (with led=1)
    median: data.md[i],                 // the row's rolling median at the time
    first_sat: data.s[i],
    mined_height: data.sh[i],
    sats_sold: data.n[i],
    txid_short: data.x[i],              // first 8 + last 4 hex digits
    // The rows of the other sats it sold, primary-first ({black_omega: 1}).
    secondary: (data.f[i] & 16) ? pairs(data.w[w++]) : {},
    // The sats it sold by exact type combination ([{types: ['uncommon', 'alpha'], sats: 1}]).
    types: kind(data.kinds[data.k[i]])
  };
});
  • Follow new sales: keep seq and ask ?after=<seq> (same row/group) on the next block; append what comes back. If generation changes (a reorg), reload from after=0. A cursor more than 5,000 sales behind the newest is answered whole: the answer's after is then 0, and replaces what you hold. One past the newest is answered as the newest (nothing new).
  • Secondary rows: a bundle is shown as its primary row (the primary-sat rule); a sale flagged 16 sold sats of other rows too, named by its item in w. With led=1, a row's view also lists the sales led by another row that it is a secondary row of, flagged 8 (with their own row in o), never in its median or market.
  • Exact types: a row's sales are not all alike (an alpha uncommon sold with a plain uncommon is an Uncommon sale). k names the type combinations each sale sold, its own row's first, through the small kinds and type_sets tables.
  • Addresses are not in the columns: ask Sale parties for the rows you show.
  • The full txid is a redirect away: /api/sales/{seq}/tx (to the transaction page; {txid, href} with accept: application/json).
  • A sale's price is the marketplace's: on Magisat the listing price; elsewhere the seller's payment plus attributable marketplace fees. Its unit price is the premium (price less the sold output's value) per sat of its row.

To filter by mint year, mint day, birthday (month-day) or a rarer sub-category, newest first and paged, use Query sales; see Filtering & birthday sats.

Pending sales #

pending lists the selection's sales still in the mempool, newest first, under the same row, group, sub and led rules. Each has the market overview's sale fields (row, u, p, n, s, secondary, seller, buyer, …), placed among the rows exactly as a confirmed sale, plus key (txid:vin), the whole txid, vin, first_seen and fee_rate; seq and h are null. They are never in the columns, count or seq, and never in a median, volume or stat: once mined, the same sale (same txid and vin) arrives in the columns and the pending one is gone. See Pending.