raresats.net API
Trading API View as Markdown

Prepare a purchase

POST/api/market/purchase/prepare

Step 1 of buying 1-20 listings: checks they are active and unspent, picks plain cardinal UTXOs of your payment address, and returns the PSBTs to sign (a setup PSBT first when you lack padding), the fee breakdown and an expiry. The plan is held 10 minutes.

Request body #

content-type: application/json

FieldTypeRequiredDescription
listing_idsarray of stringyes1 to 20 (max_bulk) active listings, each once. (1-20 items)
buyerobjectyesThe buyer's wallet: Xverse-style wallets have two addresses, UniSat-style one (use it for both).
buyer.payment_addressstringyesPays: padding, funding and change. Taproot, native SegWit or nested SegWit.
buyer.payment_public_keystringyesIts public key, hex (Taproot: the internal key, x-only or compressed; SegWit: compressed).
buyer.ordinals_addressstringyesReceives the bought UTXOs. Taproot, native SegWit or nested SegWit. May equal payment_address.
buyer.ordinals_public_keystring | nullnoIts public key, hex. Optional; checked when given.
fee_ratenumberyesNetwork fee rate in sat/vB: at least minimum from GET /api/market/fees (and at least 1), at most 5000. (max 5000)
affiliatestring | nullnoOptional: the referring affiliate's address. It wins over the rs_ref cookie the site sets from a referral link. Any address of the network is an affiliate, with no registration; an unusable one (another network's, or the fee address) is ignored (invalid), never refused.

Example request #

cURL
curl -X POST 'https://raresats.net/api/market/purchase/prepare' \
  -H 'content-type: application/json' \
  -d '{
  "listing_ids": ["1031931af1592e1c"],
  "buyer": {
    "payment_address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
    "payment_public_key": "039b3b694b8fc5b5e07fb069c783cac754f5d38c3e08bed1960e31fdb1dda35c24",
    "ordinals_address": "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
    "ordinals_public_key": "03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115"
  },
  "fee_rate": 4,
  "affiliate": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh"
}'
JavaScript
const response = await fetch('https://raresats.net/api/market/purchase/prepare', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    "listing_ids": ["1031931af1592e1c"],
    "buyer": {
      "payment_address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
      "payment_public_key": "039b3b694b8fc5b5e07fb069c783cac754f5d38c3e08bed1960e31fdb1dda35c24",
      "ordinals_address": "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
      "ordinals_public_key": "03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115"
    },
    "fee_rate": 4,
    "affiliate": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh"
  })
});
const data = await response.json();
Python
import requests

data = requests.post("https://raresats.net/api/market/purchase/prepare", json={
    "listing_ids": ["1031931af1592e1c"],
    "buyer": {
        "payment_address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
        "payment_public_key": "039b3b694b8fc5b5e07fb069c783cac754f5d38c3e08bed1960e31fdb1dda35c24",
        "ordinals_address": "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
        "ordinals_public_key": "03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115"
    },
    "fee_rate": 4,
    "affiliate": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh"
}).json()

Response 200 #

FieldTypeDescription
idstringThe prepared purchase: submit it with this id.
setupobjectPresent only when your payment address lacks padding: a setup transaction that makes it, to sign and submit too (broadcast first).
setup.psbtstringThe unsigned PSBT, base64. The sellers' inputs carry no signature (the server adds them at submit).
setup.signarray of objectYour inputs: sign exactly these, with your wallet's default sighash.
setup.sign[].indexintegerThe input to sign.
setup.sign[].addressstringThe address whose key signs it.
setup.sign[].sighashintegerThe sighash type to sign with, when it is not the default: 131 (0x83, SIGHASH_SINGLE|ANYONECANPAY) for listings. Absent on purchase inputs: sign with the default (SIGHASH_DEFAULT for Taproot, SIGHASH_ALL for SegWit v0).
purchaseobjectThe purchase transaction to sign.
purchase.psbtstringThe unsigned PSBT, base64. The sellers' inputs carry no signature (the server adds them at submit).
purchase.signarray of objectYour inputs: sign exactly these, with your wallet's default sighash.
purchase.sign[].indexintegerThe input to sign.
purchase.sign[].addressstringThe address whose key signs it.
purchase.sign[].sighashintegerThe sighash type to sign with, when it is not the default: 131 (0x83, SIGHASH_SINGLE|ANYONECANPAY) for listings. Absent on purchase inputs: sign with the default (SIGHASH_DEFAULT for Taproot, SIGHASH_ALL for SegWit v0).
summaryobjectThe fee breakdown and what you buy.
summary.pricesintegerSum of the listing prices.
summary.platform_feeintegerThe platform fee the buyer pays: max(ceil(prices × fee_bps / 10000), min_fee) (2.5%, at least 546 sats), the same with or without a referrer. Split between platform_share and affiliate_fee.
summary.platform_shareintegerThe part of platform_fee paid to the fee address (all of it without an applied referral).
summary.affiliate_feeintegerThe part paid to the affiliate, in its own output right after the platform's (0 unless affiliate_status is applied).
summary.affiliate_addressstring | nullThe referrer (normalized), or null when none was given or it was not a usable address.
summary.affiliate_statusstringnone (no referrer), invalid (not a usable address of the network, or the fee address), ignored_self (a self-referral; see affiliate_reason), applied (paid), below_minimum (a share would be under 546 sats: the platform keeps the fee).
summary.affiliate_reasonstring | nullWhy an ignored_self referral was not paid: same_address (the referrer is the buyer's payment or ordinals address), affiliate_wallet (the buyer's address belongs to the wallet that registered the affiliate, when it used the optional registration), linked_funds (the buyer's coins came from the affiliate's addresses, or the affiliate's from the buyer's), unchecked (the funds could not be checked in time: no split), seller (the referrer is a seller's address or payout). null otherwise.
summary.affiliate_share_bpsintegerThe affiliate's share of the fee in force, basis points.
summary.network_feeintegerNetwork fee of the purchase plus the setup transaction.
summary.purchase_network_feeintegerNetwork fee of the purchase transaction.
summary.setup_network_feeintegerNetwork fee of the setup transaction (0 without one).
summary.totalintegerWhat the purchase costs you: prices + platform_fee + network_fee. The padding you spend comes back to you.
summary.vsizeintegerVirtual size of the purchase transaction, vB.
summary.setup_vsizeintegerVirtual size of the setup transaction, vB (0 without one).
summary.fee_ratenumberThe fee rate asked for, sat/vB.
summary.changeintegerChange back to your payment address (0: none; a remainder below dust goes to the network fee).
summary.padding_valueintegerValue of each new padding output (600).
summary.itemsarray of objectThe listings bought, in purchase order.
summary.items[].idstringThe listing.
summary.items[].outpointstringIts UTXO.
summary.items[].valueintegerThe UTXO's value (it lands whole in your ordinals address).
summary.items[].priceintegerThe listing price.
summary.items[].unit_pricenumberThe listing's unit price.
summary.items[].rowstringIts own row.
summary.items[].countintegerSats of that row.
expires_atintegerUnix seconds: the plan is held 10 minutes. Submit before then, or prepare again.
Response
{
  "id": "5c9e5c410688cd85",
  "purchase": {
    "psbt": "cHNidP8BAP3lAQIAAAAEV/MhdPD/0sIHo8hX35p9GQud7geCW9jXa3hoAejEjgoAAAAAAP3///+3Sw6TNUSithUu7/zVH0UQDemfGc8HWcjxkuB/9nd1LAAAAAAA/f///6nTU92uYg6gtb+FEVnT5JxNFJ1H13OjJCln4+xg+7YvAAAAAAD9////VHDf42dMx3y1IrYZcBnC/YiD1iNsIqK5yRDtpRMrGUYAAAAAAP3///8JsAQAAAAAAAAXqRQ/tulYEuV7tGkfmkpiiGKmGk92m4ciAgAAAAAAACJRIKYIafDbzx3GWcnOy6+AUBNeqejNxIcFPx3GiAlJ3GhMguwAAAAAAAAWABTAzrzWw9PKjHXcXsYuvlUzDvkQ4vUCAAAAAAAAIlEgrfKcXTOb9Ymo27aSDRsOplytPApoV4riNE46JGW7F/D1AgAAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7WAIAAAAAAAAXqRQ/tulYEuV7tGkfmkpiiGKmGk92m4dYAgAAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abh3hIAQAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHAAAAAAAAAAAOagxyYXJlc2F0cy5uZXQAAAAAAAEBIFgCAAAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHAQQWABT5kGeayv4lwnYVNztAvyJEbST/RAABASBYAgAAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abhwEEFgAU+ZBnmsr+JcJ2FTc7QL8iRG0k/0QAAQEfIgIAAAAAAAAWABTAzrzWw9PKjHXcXsYuvlUzDvkQ4gABASDwSQIAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abhwEEFgAU+ZBnmsr+JcJ2FTc7QL8iRG0k/0QAAAAAAAAAAAAA",
    "sign": [
      {"index": 0, "address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"},
      {"index": 1, "address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"},
      {"index": 3, "address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"}
    ]
  },
  "summary": {
    "prices": 60546,
    "platform_fee": 1514,
    "platform_share": 757,
    "affiliate_fee": 757,
    "affiliate_address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
    "affiliate_status": "applied",
    "affiliate_reason": null,
    "affiliate_share_bps": 5000,
    "network_fee": 2652,
    "purchase_network_fee": 2652,
    "setup_network_fee": 0,
    "total": 64712,
    "vsize": 663,
    "setup_vsize": 0,
    "fee_rate": 4,
    "change": 84088,
    "padding_value": 600,
    "items": [
      {
        "id": "1031931af1592e1c",
        "outpoint": "2fb6fb60ece3672924a373d7479d144d9ce4d3591185bfb5a00e62aedd53d3a9:0",
        "value": 546,
        "price": 60546,
        "unit_price": 60000,
        "row": "black_rare",
        "count": 1
      }
    ]
  },
  "expires_at": 1790953585
}

Errors #

{error, code}; see Errors for every code.

StatusCodeMeaning
400bad_addressAn address is not a mainnet address (or not one at all).
400bad_buyerbuyer is not an object.
400bad_fee_ratefee_rate is not a positive number up to 5000.
400bad_idsids / listing_ids is empty, too long, malformed, or lists an id twice.
400bad_jsonThe body is not valid JSON.
400bad_public_keyA public key is not hex, or not a valid secp256k1 key of the right size.
400bad_requestThe body is not a JSON object, or the engine refused a malformed request (e.g. a purchase submitted without its signed setup PSBT).
403cross_originA POST from a browser on another site (a foreign Origin, or sec-fetch-site: cross-site). Server-side clients send no Origin and are not affected.
404not_foundNo such listing or offer. In chat: no such message. A display name to remove that does not exist.
405method_not_allowedWrong method for the route; the allow header lists the right ones.
409not_activeA listing (or offer) is not active: being bought (or accepted), sold, cancelled, moved, expired or invalid. Carries id and status.
409spentThe UTXO (or a listing's UTXO) is spent or does not exist.
413too_largeThe body is over 256 KiB.
415not_jsonA POST without content-type: application/json.
422bad_structureThe listing PSBT is not the expected layout (pairing, version 2, locktime 0, one seller, sequences), or a purchase would pay the fee address.
422dust_outputA bought UTXO is below the dust limit of your ordinals address type: use a Taproot or native SegWit ordinals address.
422fee_rate_too_lowfee_rate is below the current minimum (GET /api/market/fees).
422fee_too_highThe network fee would exceed the per-transaction cap (200,000 sats): lower the fee rate or move fewer.
422insufficient_fundsThe payment address's plain UTXOs cannot pay prices, fee, padding and network fee (or a transfer's fees). Carries available, prices, platform_fee.
422key_mismatchThe public key does not control the address.
422legacy_addressA legacy (P2PKH, 1…) address: use Taproot or SegWit.
422pubkey_mismatchThe trade engine found the public key is not the address's key.
422too_many_itemsToo many UTXOs in a listing, or listings in a purchase (max_bulk); for a wallet transfer, too many rare sats (and the runs between them) to lay out in one UTXO: choose fewer.
422unsupported_addressAn address type the marketplace does not trade (only Taproot, native SegWit and nested SegWit).
422unsupported_scriptThe trade engine met a script type it does not support.
429rate_limitedOver 30 POSTs a minute from your IPv4 address or IPv6 /64 (in chat, counted per IPv4 address or IPv6 /64: 30 writes and 20 login nonces a minute, 20 sign-ins and 10 display-name changes an hour; one message every 3 s and 20 a minute per address); wait retry-after seconds. With the AI moderation on, at most 6 messages and names a minute are checked per address and per IPv4 address or IPv6 /48.
503bitcoind_unavailablebitcoind is not available.
503busyToo many purchases, offers or transfers are being prepared: try again in a minute. Each client (IPv4 address or IPv6 /64) keeps its 10 latest prepared purchases, offers and transfers; an older one answers unknown_purchase / unknown_offer / unknown_transfer.
503engine_unavailableThe trade engine is not available.
503fork_unavailableThe rare-sat index is not available.
503market_unavailableThe marketplace is closed right now (its backends disagree on the network, or are unreachable).
503oracle_unavailableThe ord oracle is not available.
Error (409)
{
  "error": "listing 1031931af1592e1c is pending",
  "code": "not_active",
  "id": "1031931af1592e1c",
  "status": "pending"
}

Then sign the setup PSBT (when present) and the purchase PSBT, exactly the inputs in their sign lists, and submit before expires_at. The whole flow: Buy listings.

Affiliates #

affiliate names the referrer of the purchase: any address of the network but the fee address, with no registration; it is never paid for a self-referral. Without it, the server uses the rs_ref cookie the site sets when a buyer follows a referral link (?ref=<address> or /r/<address>); the body's field wins. The summary says what happened (affiliate_status) and how the platform fee is split (platform_share, affiliate_fee). A referrer never makes prepare fail. See Affiliates.