Listing
A listing sells one unit that sits alone on its own attached asset UTXO.
The seller signs that UTXO with SIGHASH_SINGLE|ANYONECANPAY (0x83) against a
payment output worth the price. A buyer later completes that signature into a
checkout transaction.
You need the seller’s session and a signer that can
sign the returned PSBT input with SINGLE|ANYONECANPAY. Listing a prepared
unit needs no new Bitcoin transaction; preparing a detached balance does.
Choose the path that matches your inventory:
| Path | When | Bitcoin transactions |
|---|---|---|
| List prepared inventory | The unit is already on its own asset UTXO | None: listing broadcasts nothing |
| Attach-and-list | The unit is still a detached address balance | One attach, then the listing activates after it confirms |
List prepared inventory
Section titled “List prepared inventory”- Read prepared inventory. It needs no session and returns exact outpoints.
POST /v1/listing-preflightswith anidempotency_key,seller,asset,asset_utxo: {txid, vout}, andprice_sats.- Require
mode: "reuse",status: "ready", and the same outpoint and price. - Sign
listing.psbt_hex: onlylisting.inputs_to_sign, withSINGLE|ANYONECANPAY. Do not sign inputs outside that instruction. POST /v1/listing-preflights/:id/listingwithpsbt_hexandexpires_at(Unix seconds, ornullfor no marketplace expiry).
curl -s "$API/v1/address/bc1q.../inventory/prepared?asset=RAREPEPE"
curl -s -X POST "$API/v1/listing-preflights" \ -H 'content-type: application/json' -H "x-session-token: $TOKEN" \ -d '{ "idempotency_key": "inventory-001-0", "seller": "bc1q...", "asset": "RAREPEPE", "asset_utxo": { "txid": "aaaa...aaaa", "vout": 0 }, "price_sats": 100000 }'
curl -s -X POST "$API/v1/listing-preflights/$PREFLIGHT_ID/listing" \ -H 'content-type: application/json' -H "x-session-token: $TOKEN" \ -d '{"psbt_hex":"70736274ff...","expires_at":null}'Providing asset_utxo is a prepared-only instruction: an unavailable output is
refused, never replaced and never attached again. The idempotency key binds the
seller, asset, price, and outpoint, so repeating the exact request after a lost
response returns the same preflight.
An unsigned draft of a prepared unit holds nothing: a new draft at another
price may name the same unit, and the first signed listing wins (a later
signature on the other draft is refused). Without asset_utxo, the server
picks units no live draft names first, so several drafts opened together get
different units. Before signing you can also change a draft’s price with
POST /v1/listing-preflights/:id/price, or drop it with
POST /v1/listing-preflights/:id/discard (it does not detach anything).
preflight.next_action tells you what the preflight is waiting for:
sign_attach, wait_for_indexer, set_price, sign_listing, list_again
(status withdrawn: a reorg withdrew the listing and the attach is mined
again, so list the attached unit again with a new preflight), or null when
it is finished.
Prepare units first
Section titled “Prepare units first”Preparation attaches one unit per transaction (raw quantity 1 for an
indivisible asset, 100000000 for a divisible one). There is no server-side
preparation job: the server composes and checks, you sign and relay.
GET /v1/address/:address/inventory/attachablefor detached balances.POST /v1/attach-templateswithutxo_owner,asset, and optionallyasset_source,fee_rate,exclude,in_flight, andfunding. It returns one verified one-unit attach PSBT, its signer groups, the change output, and a fee quote. It writes nothing and reserves nothing.- Sign it, then relay the finalized bytes with
POST /v1/transactions(raw_tx_hex). - Track it yourself.
POST /v1/transactions/statuschecks up to 25 txids;finalmeans at least 3 confirmations.
Keep your own state per attach, because the server keeps none:
- Pass the inputs you already spent in earlier attaches as
exclude, so server coin selection skips them. - Declare your signed-but-not-final attaches as
in_flight(asset, raw units, andasset_utxoonce known). The server only ever uses this to refuse composing units those attaches already use. - To prepare several units from one coin without waiting for a block, fund each
attach from the previous one’s change: pass the template’s
changeobject (txid,vout,value_sats,script_pub_key_hex) back asfunding: [change]. Without explicitfundingthe server selects only confirmed coins, and answers422withreason: "no_funding"and apending_confirmationcount while yours are still unconfirmed. - Save signed bytes before you broadcast. An uncertain relay retries the exact same bytes; it never re-signs.
Speed up a stuck attach
Section titled “Speed up a stuck attach”If an attach waits in the mempool because fees rose, pay more from its own change with a child transaction (CPFP). The server keeps nothing for this either:
POST /v1/attach-templates/speed-upwithowner(your session address),txid(the attach), and optionallyfee_rate(default: the next-block rate). For a chain of attaches, pass the newest one: its change is the unspent one, and the child is sized over every unconfirmed ancestor.- Check
result.child: one input (the attach’schange, never output 0, the asset UTXO), one output back to you worthfinal_change_sats, transaction version 2, locktime 0.fees.package_fee_rateis what the attach, its unconfirmed ancestors, and the child pay together. - Sign input 0 (
DEFAULT|ALL), finalize, and relay it withPOST /v1/transactions. Do not spend that change again in a later attach.
A refusal names its reason: confirmed, fee_sufficient (already pays the
rate), change_spent (with spent_by: speed up that transaction instead),
no_change, ancestor_limit (Bitcoin’s 25-transaction chain limit), or
insufficient_change (insufficient_funds with need_sats and have_sats).
After signing and finalizing the child locally, verify that its txid matches
result.child.expected_txid before relaying it.
The source pays the Bitcoin fee and Counterparty’s attach fee in XCP. The XCP
figure is a quote until the attach is parsed; do not present it as a miner or
marketplace fee. An attach that cannot be paid for fails with
insufficient_asset_balance or insufficient_funds, whose bodies say exactly
what is short.
Attach-and-list
Section titled “Attach-and-list”Omit asset_utxo from POST /v1/listing-preflights and the API chooses. If the
seller already holds an unlisted, verified one-unit asset UTXO of that asset, it
reuses it (mode: "reuse", status: "ready": sign the listing as above and
nothing broadcasts). Otherwise it composes an attach for you (mode: "attach",
status: "awaiting_attach_signature"). Branch on mode; pass asset_utxo when
you need a specific unit. An attach response carries an attach signing
instruction and the dependent listing template. The two are shaped
differently: the listing says listing.inputs_to_sign, while the attach names
its signers, because a legacy asset_source may sign some of its inputs:
"attach": { "psbt_hex": "70736274ff...", "signers": [{ "address": "bc1p...", "inputs_to_sign": [0] }], "sighash": "ALL", "expected_txid": "0d9a..."}Sign only the groups whose address you control. For witness-only inputs,
check the finalized txid against expected_txid. A P2PKH source can change the
attach txid during signing; use the final outpoint returned after submission.
POST /v1/attach-templates returns the same attach object.
- Sign both and send them together to
POST /v1/listing-preflights/:id/attach-and-list(attach_psbt_hex,listing_psbt_hex,expires_at). Only the attach broadcasts now. - Or sign the attach alone,
POST /v1/listing-preflights/:id/attach, pollGET /v1/listing-preflights/:iduntilstatusisready, then sign and submit the listing.
The listing becomes public only after the attach confirms on Bitcoin, Counterparty binds the exact outpoint, and the actual XCP fee is reconciled. The API stores the signed attach before broadcasting and rebroadcasts it on its own if needed.
Reprice
Section titled “Reprice”Repricing needs a fresh signature, not another attach.
Review result.current_price_sats and the new result.listing template. Verify
the PSBT against the new price, then sign only listing.inputs_to_sign with
SINGLE|ANYONECANPAY and submit the signed PSBT.
Routes: POST /v1/listings/:id/reprice-preflight (price_sats), then
POST /v1/listings/:id/reprice (psbt_hex). The preview shows the current price,
but it is not a lock: do not run two repricers against the same inventory.
Delist
Section titled “Delist”POST /v1/listings/:id/delist with the owner’s session. No signature needed.
curl -s -X POST "$API/v1/listings/$LISTING_ID/delist" -H "x-session-token: $TOKEN"Neither delist nor reprice can overwrite a settlement already in progress; you get a conflict instead.
Reprice or delist up to 20 at once
Section titled “Reprice or delist up to 20 at once”POST /v1/listings/batch is the collection-level form of the per-listing routes.
It takes an action and 1..20 distinct listings, every one sold by the session
address, and runs each item through the single route’s own checks.
| Action | Body | Answer |
|---|---|---|
reprice_preflight |
listings: [{listing_id, price_sats}] |
{listings: [...]}, one reprice template per listing, in order |
reprice |
listings: [{listing_id, psbt_hex}] |
{listings: [{listing_id, ok: true, price_sats}]} |
delist |
listings: [{listing_id}] |
{listings: [{listing_id, ok: true}]} |
For each returned preflight, verify the new price and sign
listing.inputs_to_sign with SINGLE|ANYONECANPAY. Submit the resulting
{listing_id, psbt_hex} entries together with action: "reprice".
- The reprice preflight and submit are validated whole: if any item is
refused (not yours, not active, a counter, same price, below the floor, spent,
or a signature that fails), the batch is refused with the first refusal’s
status and code, and
refused_listings: [{index, listing_id, status, code, error}]lists every refused item. Nothing is written. - A buyer who returned a valid signature between verification and write keeps
priority: that item comes back
{ok: false, code: "state_changed"}and the others are repriced. - The batch delist refuses whole only for a missing (
404) or foreign (403) listing. One that already sold, expired, was delisted, or is mid-purchase comes back{ok: false, code: "state_changed"}; the rest are delisted.
Reading your listings
Section titled “Reading your listings”| Route | Session | Returns |
|---|---|---|
GET /v1/address/:address/listings |
no | Public active listings for a seller |
GET /v1/address/:address/managed-listings |
owner | Active and pending listings, including private counters |
GET /v1/address/:address/ended-listings |
owner | Listings that ended in the last 30 days, each with a reason |
GET /v1/address/:address/listing-preflights |
owner | Your preflights |
Use preflights for both attached inventory and new attaches. They give you idempotent creation and server-side recovery.
Bulk listing is a set of independent orders, not an atomic batch. Some can succeed before a later one is rejected.