# Errors and limits

## The error object

Every `/api/market/*` route answers an error with its HTTP status and:

```json title="Error"
{
  "error": "the cancel message is more than 10 minutes old: sign a new one",
  "code": "stale_message"
}
```

- `code` is stable and machine-readable: branch on it. `error` is for people and may change.
- Some codes carry extra fields: `outpoint`, `id` and `status` (`not_active`), `txid` (`broadcast_unknown`), `reason` and `setup_txid` (`conflict`, `rejected`), `available`, `prices` and `platform_fee` (`insufficient_funds`).
- The data routes (`/api/market`, `/api/sales`, `/api/sat/…`, `/api/address/…`, `/api/rows/…` and the feeds) answer `{"error": "..."}` with no code.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | string | yes | A human-readable message. Show it; do not parse it. |
| `code` | string | yes | A stable machine-readable reason (see the error code table). |
| `outpoint` | string | no | The outpoint the error is about, when there is one. |
| `id` | string | no | The listing the error is about, when there is one. |
| `status` | string | no | That listing's status (`not_active`). |
| `txid` | string | no | The purchase transaction (`broadcast_unknown`). |
| `setup_txid` | string | no | The setup transaction, when it was already broadcast (`conflict`, `rejected`). |
| `reason` | string | no | bitcoind's rejection reason (`conflict`, `rejected`). |
| `available` | integer | no | Sats the payment address can spend (`insufficient_funds`). |
| `prices` | integer | no | Sum of the listing prices (`insufficient_funds`). |
| `platform_fee` | integer | no | The platform fee (`insufficient_funds`). |
| `violations` | array of object | no | Broken purchase invariants (`invariant_violated`). |
| `violations[].rule` | string | yes | The rule broken |
| `violations[].message` | string | yes | What is wrong |
| `next_change_at` | integer | no | When the address may change its display name again, unix seconds (`name_change_too_soon`). |
| `retired` | object | no | On a 404 for a retired row (`retired_row`): the row and where its sats are now. |
| `retired.row` | string | yes | The retired row asked for. |
| `retired.successor` | object \| null | yes | The parent row and years that hold its sats; null for a row retired outright (`alpha_jan_feb_2009`). |
| `retired.successor.row` | string | yes |  |
| `retired.successor.years` | array of integer | yes |  |
| `category` | string | no | Why the AI moderation refused the text (`moderated`): harassment, hate, sexual, violence, doxxing, scam, spam, malware, illegal, impersonation, manipulation or other. |
| `until` | integer | no | When the mute ends, unix seconds (`muted`; on `moderated` when that refusal muted the address). |

## Statuses

| Status | Meaning | Retry? |
| --- | --- | --- |
| 400 | A malformed request. | No: fix it. |
| 403 | A cross-origin browser POST. | No: call from a server. |
| 404 | No such route, listing, outpoint listing or prepared purchase. | No. |
| 405 | Wrong method; see the `allow` header. | No. |
| 409 | A conflict with the chain or the store: spent, sold, changed, expired, a lost race. | Re-read, then prepare again. |
| 413 | Body over 256 KiB. | No. |
| 415 | A POST that is not `application/json`. | No. |
| 422 | A marketplace rule the request breaks. | No: change the request. |
| 429 | Rate limited. | After `retry-after` seconds. |
| 500 | A server bug. | Report it. |
| 503 | bitcoind, the rare-sat index or the ord indexer is unavailable or catching up; or `broadcast_unknown`. | Yes, after a short wait (`broadcast_unknown`: submit the same purchase again). |

## Every error code

| Code | Status | Meaning |
| --- | --- | --- |
| `bad_address` | 400 | An address is not a mainnet address (or not one at all). |
| `bad_addresses` | 400 | `addresses` must list 1 to 200 addresses. |
| `bad_bidder` | 400 | The offer's `bidder` is missing, not an object, or lacks `ordinals_public_key`. |
| `bad_buyer` | 400 | `buyer` is not an object. |
| `bad_config` | 500 | The marketplace is misconfigured. |
| `bad_contains` | 400 | `contains` is not 0, 1 or `only`, or is set without `row`. |
| `bad_expiry` | 400 | `expires_in` is not 86400, 259200, 604800 or 2592000 (seconds), `null` or `"never"`. |
| `bad_fee_rate` | 400 | `fee_rate` is not a positive number up to 5000. |
| `bad_id` | 400 | `id` is not a prepared purchase or transfer id. |
| `bad_ids` | 400 | `ids` / `listing_ids` is empty, too long, malformed, or lists an id twice. |
| `bad_items` | 400 | `items` is not a list of 1-50 UTXOs (or the PSBT lists more), or an item's `sats` is not null or a list of sat numbers. |
| `bad_json` | 400 | The body is not valid JSON. |
| `bad_message` | 400 | The cancel message is not `raresats.net cancel <ids> <unix seconds>` or names other listings than `ids`; or the registration message is not `raresats.net affiliate <address> <unix seconds>` for `address`. |
| `bad_nonce` | 422 | The login nonce was not handed out for this address, has expired (10 minutes) or was used: ask for a new one. |
| `bad_order` | 400 | `order` is not `asc` or `desc`. |
| `bad_outpoint` | 400 | Not an outpoint (`txid:vout`). |
| `bad_outpoints` | 400 | `outpoints` lists none, or more than 100. |
| `bad_paging` | 400 | `limit` or `offset` out of range. |
| `bad_path` | 400 | The path is not valid percent-encoding. |
| `bad_payout` | 422 | A listed input is paired with an output that pays no address, or the payout address is the marketplace's fee address. |
| `bad_price` | 400 | A price is not a whole number of sats. |
| `bad_psbt` | 400 | Not a base64 PSBT, or one that does not parse. |
| `bad_public_key` | 400 | A public key is not hex, or not a valid secp256k1 key of the right size. |
| `bad_request` | 400 | The body is not a JSON object, or the engine refused a malformed request (e.g. a purchase submitted without its signed setup PSBT). |
| `bad_row` | 400 | `row` is not a row id. |
| `bad_sequence` | 422 | A listed input is not signed with sequence 0xfffffffd. |
| `bad_sighash` | 422 | A signature uses the wrong sighash type (listings: 0x83; purchases: DEFAULT or ALL). |
| `bad_signature` | 400, 422 | 400: `signature` is missing or too long. 422: a signature does not verify (cancel or registration message, listing or purchase input). |
| `bad_sort` | 400 | `sort` is not `unit`, `price` or `new`. |
| `bad_status` | 400 | `status` is not `all` or a comma-separated list of offer statuses, or (wallet transfers) not `pending` or `all`. |
| `bad_structure` | 422 | The listing PSBT is not the expected layout (pairing, version 2, locktime 0, one seller, sequences), or a purchase would pay the fee address. |
| `bad_sub` | 400 | `sub` is not 0 or 1. |
| `bad_types` | 400 | `types` is not a comma-separated list of activity event types. |
| `bad_wallet_addresses` | 400 | `wallet_addresses` is not a list of at most 4 mainnet addresses. |
| `banned` | 403 | The address is banned from chat: it may not post or set a display name. |
| `bitcoind_unavailable` | 503 | bitcoind is not available. |
| `broadcast_unknown` | 503 | The broadcast's outcome is unknown: submit the same purchase or transfer again to settle it. Carries `txid`. |
| `busy` | 503 | Too 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`. |
| `changed` | 409 | The UTXO's value is not what the PSBT says, or (wallet tools) a UTXO, its sats or the transaction being replaced changed since prepare: prepare again. |
| `chat_busy` | 429 | Too many chat messages and names are being checked across the site right now (over the global cap of model calls a minute); wait `retry-after` seconds. |
| `chat_disabled` | 404 | Chat and display names are off on this server. |
| `config_changed` | 409 | The marketplace settings changed since this purchase or transfer was prepared (prepare it again), or since this offer was made (it is now `invalid`, reason `config_changed`). |
| `conflict` | 409 | A listing or one of your UTXOs was spent meanwhile (lost race), or bitcoind would not replace a transaction already spending it (`insufficient fee`), or a transfer's inputs are spent by another transaction. |
| `cross_origin` | 403 | A 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. |
| `duplicate` | 409 | The same message (ignoring case) from the same address within 2 minutes. |
| `duplicate_outpoint` | 400 | The same UTXO twice in one listing. |
| `dust_output` | 422 | A bought UTXO is below the dust limit of your ordinals address type: use a Taproot or native SegWit ordinals address. |
| `empty_text` | 422 | Nothing is left of the message once control, zero-width and bidi characters are dropped and spaces collapsed. |
| `engine_unavailable` | 503 | The trade engine is not available. |
| `expired` | 409 | The prepared purchase, offer or transfer expired, or the offer itself has: prepare it again. |
| `fee_rate_too_low` | 422 | `fee_rate` is below the current minimum (`GET /api/market/fees`). |
| `fee_too_high` | 422 | The network fee would exceed the per-transaction cap (200,000 sats): lower the fee rate or move fewer. |
| `fees_unavailable` | 503 | No fee estimate is available. |
| `fork_behind` | 503 | The rare-sat index has not caught up with bitcoind: try again in a minute. |
| `fork_unavailable` | 503 | The rare-sat index is not available. |
| `funding_spent` | 409 | A UTXO of the bidder's that funds the offer was spent (or is no longer plain cardinal): the offer is no longer valid. |
| `future_message` | 422 | The cancel or registration message is dated more than 2 minutes ahead: check the clock. |
| `indexer_behind` | 503 | The ord indexer is catching up: try again shortly. |
| `inscribed` | 422 | The UTXO carries an inscription: only rare sats are listed; the wallet tools only send it whole. |
| `insufficient_funds` | 422 | The payment address's plain UTXOs cannot pay prices, fee, padding and network fee (or a transfer's fees). Carries `available`, `prices`, `platform_fee`. |
| `internal` | 500 | A server bug. Report it. |
| `invariant_violated` | 500 | A planned or finalized purchase or transfer broke an invariant (`violations`); nothing was broadcast. |
| `key_mismatch` | 422 | The public key does not control the address. |
| `legacy_address` | 422 | A legacy (P2PKH, `1…`) address: use Taproot or SegWit. |
| `listing_changed` | 409 | A listing's stored terms no longer match the plan: prepare it again. |
| `market_unavailable` | 503 | The marketplace is closed right now (its backends disagree on the network, or are unreachable). |
| `method_not_allowed` | 405 | Wrong method for the route; the `allow` header lists the right ones. |
| `missing_signature` | 422 | An input that must be signed is not. |
| `moderated` | 422 | The AI moderation refused the chat message or display name; `category` says why (the message is not posted, the name not taken). Three refusals in 10 minutes mute the address for an hour (`until`). |
| `moderation_unavailable` | 503 | The AI moderation could not check the text right now (timeout or model error): nothing was posted. Try again in a moment. |
| `muted` | 403 | The address is muted (by the AI moderation after repeated refusals: 3 in 10 minutes), or its network (an IPv4 address or IPv6 /48) is paused after 6 refusals within an hour, until `until`, unix seconds: it may not post or set a display name until then. |
| `name_address` | 422 | A display name must not look like an address (`bc1…`, base58) or a hash. |
| `name_change_too_soon` | 429 | A display name changes at most once in 24 hours; `next_change_at` says when. |
| `name_chars` | 422 | A display name uses only letters, digits, spaces and `_ . -`, and starts and ends with a letter or digit. |
| `name_length` | 422 | A display name is 3 to 24 characters. |
| `name_reserved` | 422 | The display name is reserved (raresats, admin, moderator, support, official, satoshi and the like). |
| `name_taken` | 409 | Another address has that display name (compared without case, separators or look-alike digits), gave it up less than 7 days ago, or it was removed by a moderator and is never given out again. |
| `no_endpoint` | 404 | No such `/api/market/*` route. |
| `no_row` | 422 | The UTXO holds no sat of a market row. |
| `no_session` | 401 | No chat session: sign in (`POST /api/session`) and send its cookie. |
| `not_active` | 409 | A listing (or offer) is not active: being bought (or accepted), sold, cancelled, moved, expired or invalid. Carries `id` and `status`. |
| `not_admin` | 403 | Only a chat admin (`SUPPLY_CHAT_ADMINS`) may do that. |
| `not_bannable` | 422 | An admin cannot be banned. |
| `not_bidder` | 422 | An offer was not made by `address` (neither its payment nor its ordinals address). |
| `not_found` | 404 | No such listing or offer. In chat: no such message. A display name to remove that does not exist. |
| `not_json` | 415 | A POST without `content-type: application/json`. |
| `not_listed` | 404 | That outpoint has no live listing. |
| `not_owner` | 422 | The UTXO does not belong to the seller address, or (wallet tools) to the owner's addresses, or the transfer was made by another wallet. |
| `not_seller` | 422 | A listing was not listed by `address`. |
| `offer_changed` | 409 | The offer's stored signatures no longer verify: it cannot be accepted. |
| `oracle_unavailable` | 503 | The ord oracle is not available. |
| `own_utxo` | 422 | The UTXO is held by one of the bidder's own addresses. |
| `pending` | 409 | The UTXO's listing is being bought, or an offer of it accepted, right now. |
| `price_too_high` | 422 | The price is above 21,000,000 BTC. |
| `price_too_low` | 422 | The price is at most the UTXO's own value, below 546 sats, or below the payout output's dust limit. |
| `psbt_mismatch` | 422 | The signed PSBT is not the prepared transaction. |
| `pubkey_mismatch` | 422 | The trade engine found the public key is not the address's key. |
| `rate_limited` | 429 | Over 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. |
| `rejected` | 422 | bitcoind refused the transaction (`reason`). |
| `replayed` | 409 | That cancel or registration message was used already. |
| `retired_row` | 404 | `row` is a row the site retired (`uncommon_2009`, `rare_2009`): its listings are its parent row's. `retired` names the row and the parent and years to ask for. |
| `room_busy` | 429 | Over 5 chat messages a second across the whole chat; wait `retry-after` seconds. |
| `runes` | 422 | The UTXO carries runes (the wallet tools only send it whole). |
| `spent` | 409 | The UTXO (or a listing's UTXO) is spent or does not exist. |
| `stale_message` | 422 | The cancel or registration message is more than 10 minutes old: sign a new one. |
| `suspicious_link` | 422 | A link in the message names a host that is not plain ASCII: look-alike letters from other scripts, punycode (`xn--`), or `user@host`. Write the link with its real host name. |
| `bad_topic` | 400 | A topic id that is not one: 1-32 of a-z, 0-9 and inner hyphens (and, to create one, not `main`). On a reply: a `topic` other than its root's. |
| `unknown_topic` | 404 | No such chat topic, or an archived one (which takes no new messages). |
| `bad_topic_text` | 422 | A topic's name must be 1-40 characters, its description at most 140. |
| `topic_exists` | 409 | A topic (or an archived one) has that id already. |
| `too_many_topics` | 422 | At most 50 chat topics: archive one first. |
| `not_archivable` | 422 | General cannot be archived. |
| `text_too_long` | 422 | A chat message is at most 500 characters. |
| `too_large` | 413 | The body is over 256 KiB. |
| `too_many_items` | 422 | Too 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. |
| `unchanged` | 409 | That is already the display name. |
| `unconfirmed` | 409 | The UTXO is not confirmed yet. |
| `unindexed` | 409 | The ord indexer does not know the UTXO yet. |
| `unknown_offer` | 404 | No such prepared offer (it expired, or the server restarted): prepare it again. |
| `unknown_purchase` | 404 | No such prepared purchase (it was dropped: your 11th prepared since, a busy server, or a restart): prepare it again. |
| `unsupported_address` | 422 | An address type the marketplace does not trade (only Taproot, native SegWit and nested SegWit). |
| `unsupported_script` | 422 | The trade engine met a script type it does not support. |
| `untracked` | 422 | The UTXO holds no rare sat the index tracks (send it whole instead). |
| `utxo_mismatch` | 409 | The PSBT's amount or script of a UTXO is not the chain's. |
| `utxos_required` | 500 | A server bug in listing verification. |
| `bad_kind` | 400 | `kind` is not one of the wallet tools (`extract`, `send`, `send_n`, `chop`, `consolidate`, `multisend`, `speedup`, `cancel`), or a send `mode` is unknown. |
| `bad_owner` | 400 | `owner` must give the payment and ordinals addresses and their public keys. |
| `bad_destination` | 422 | The destination (or a multisend recipient) is the marketplace's fee address, or missing. |
| `bad_type` | 400 | `type` (send N) is not a sat type, types joined with `+`, or `block9` / `450x`. |
| `bad_count` | 400 | `count` (send N) is not a whole number of sats of at least 1. |
| `bad_chop_size` | 400 | `chop_size` is not a whole number from 330 to 100,000,000. |
| `bad_split` | 400 | `split` (extract) is not `{type, per_output \| chunks, trim?}`: an exact type combination that is not a whole range, `per_output` 1–2,000 or `chunks` 2–100 (exactly one), `trim` a boolean; or an item names `sats` (a split takes every sat of its type). |
| `bad_sends` | 400 | `sends` must list 1-50 payments of `{address, value}`, each at least its address type's minimum (330, or 546 for P2SH and P2PKH). |
| `payment_destination` | 422 | Rare sats, inscriptions or runes would go to your own payment address, which your wallet spends as plain bitcoin: send them to your ordinals address, or say `confirm_payment_destination: true`. |
| `address_too_large` | 422 | An owner address holds more than 5,000 outputs: the wallet tools cannot plan for it. |
| `not_plain` | 422 | A UTXO to consolidate is not plain bitcoin (it holds tracked, exotic or inscribed sats, or runes). |
| `already_extracted` | 422 | The UTXO is extracted already (its rare sats sit in an output of their own). |
| `split_chunk` | 422 | Skip code of an "extract all" over several UTXOs: the UTXO is a chunk a split on this site made (extracting it again would undo the split). Name it alone to extract it. |
| `not_enough_sats` | 422 | Your UTXOs do not hold `count` sats of that type that can be sent. Carries `available`. |
| `nothing_to_do` | 422 | Nothing to move: every item was skipped (`skipped`), or a consolidation has fewer than 2 UTXOs, or a chop leaves one piece. |
| `nothing_to_separate` | 422 | Extracting would leave the UTXO one output: its rare sats are apart already, or too close to its ends. |
| `keep_in_destination` | 422 | Exotic or unpicked tracked sats sit so close to a picked one that they would have to leave with it: send the whole UTXO instead. |
| `range_too_small` | 422 | A range chunk (send N of `block9` or `450x`) must be at least the destination's padding: 330 sats, 546 for P2SH and P2PKH. |
| `listed_public_alone` | 422 | A UTXO whose listing signature is public must be moved on its own (and fast). |
| `inscribed_consolidate` | 422 | Inscribed UTXOs are sent whole and 1:1, never merged into one output. |
| `runes_multi` | 422 | At most one rune UTXO per transfer (except a whole send merged into one output). |
| `dust_destination` | 422 | A whole UTXO is too small for the destination's address type. |
| `too_many_outputs` | 422 | The transfer would have more than 100 outputs: move fewer UTXOs at once. |
| `tx_too_large` | 422 | The transfer would exceed 100,000 vB: move fewer UTXOs at once. |
| `unknown_transfer` | 404 | No such transfer: an unknown or forgotten prepared id, or a txid the wallet tools did not build. |
| `not_pending` | 409 | The transfer to speed up or cancel is not waiting in the mempool any more (confirmed, replaced). |
| `has_descendants` | 409 | A transaction spends the transfer's outputs already: it can no longer be replaced. |
| `offer_overlap` | 409 | Since the prepare, a planned input started funding one of your offers, or a UTXO the server picked got an offer: prepare the transfer again. |
| `nothing_to_cancel` | 409 | A cancel that could save nothing: the transfer was refused (never sent), or it (and, once dropped, every transfer it replaced) only pays your own addresses. |
| `replacement_fee_too_low` | 422 | The replacement pays too little to replace the pending transaction (BIP 125): raise the fee rate to at least `min_fee_rate`. |

## Limits

- **Edge rate limit:** 50 requests per 10 seconds per IP on `/api/*` (Cloudflare). Requests over it are blocked at the edge for a short while.
- **POST rate limit:** 30 POSTs a minute per client (an IPv4 address or an IPv6 /64) on `/api/market/*`, a fixed window: 429 `rate_limited` with a `retry-after` header in seconds.
- **Prepared purchases and offers:** each client keeps its 10 latest of each; preparing an 11th drops its oldest (which then answers `unknown_purchase` / `unknown_offer` at submit).
- **Body:** JSON, `content-type: application/json`, at most 256 KiB (413 `too_large`, 415 `not_json`, 400 `bad_json`).
- **Origin:** a POST with an `Origin` header of another host, or `sec-fetch-site: cross-site`, is refused (403 `cross_origin`). No CORS headers are sent, so browsers on other sites cannot read answers either. Server-side clients (curl, Node, Python) send no `Origin` and are unaffected.
- **Sizes:** at most 50 UTXOs per listing PSBT, 20 listings per purchase (`max_bulk`), 100 ids per cancel, 100 outpoints per batch lookup, 200 listings or events per page, 500 sequence numbers per `/api/sales/parties`.
- **Caching:** marketplace answers are `no-store`. The data routes may be cached briefly (`/api/market`, `/api/sales` and `/api/sales/parties` 30 s, `/api/sat/{n}` 15 s, `/api/address/{address}` 30 s). Large answers are gzipped when you send `accept-encoding: gzip`.
