Offers
Choose an exact-target offer when you want specific asset outpoints. Choose a collection offer when any qualifying prepared unit should be eligible, including units prepared after you place it. Both can settle without the buyer returning to sign at acceptance.
You need your address’s session, asset-free BTC funding, and a signer that supports the returned PSBT instructions. The accepting seller pays the settlement fees; check their net proceeds in the acceptance quote.
| Exact-target offers | Collection offers | |
|---|---|---|
| Protocol | exact_offer_v1 |
funded_policy_offer_v1 |
| Routes | /v1/offers, /v1/exact-offer-authorizations |
/v1/policy-offers, /v1/policy-accepts |
| Covers | Only the asset outpoints the buyer signed for | Any qualifying one-unit asset UTXO, now or later, for the offer’s life |
| Buyer signs | One SIGHASH_ALL settlement per target |
A zero-fee funding parent per alternative |
| Who finishes | The named seller, alone | The seller plus the market signer |
| Placing costs | A network fee to create exact-value funding slots (unless you already have one) | Nothing: the parent is not broadcast until acceptance |
Capacity is not eligibility
Section titled “Capacity is not eligibility”A funding slot (funding_slot_id = "<txid>:<vout>") pays for one
purchase. Policy rows and exact targets only say what qualifies. One slot may back
hundreds of alternative policies and targets, but the first valid seller signature
wins and retires every sibling. For ten independent purchases, fund ten slots.
Never add up targets or policies as if they were separate liquidity.
Exact-target offers
Section titled “Exact-target offers”1. Fund slots
Section titled “1. Fund slots”Each slot is a clean BTC output worth exactly the price (plus 330 sats for attached delivery). No fee is prefunded; the accepting seller pays it.
- Already have one? Verify it:
POST /v1/offer-funding-preflightswithtxid,vout. The answer confirms it is unspent, asset-free, and yours. - Otherwise
POST /v1/offer-funding-templateswithprice_satsandslot_count(1..20). Sign and finalize the self-send locally, thenPOST /v1/funded-offerswithfunding_tx_hex, the slotbids,targets, andexpires_at. This broadcasts a Bitcoin transaction and places the policies.
2. Place policies
Section titled “2. Place policies”POST /v1/offers with existing bids, targets, and expires_at:
An asset policy can optionally include target_outpoint: {txid, vout}.
This restricts every acceptance, counteroffer and authorization to that exact
attached unit, whether listed or unlisted. Omit it for asset-wide eligibility.
Up to 20 direct unit targets are allowed per placement. Public and owner offer
reads expose target_txid and target_vout; these offers do not count toward
generic asset or collection bid depth. The asset book returns them in
direct_offers, never in offers. Different targets may reuse a funding
slot, but still represent only one possible purchase.
The target is immutable. A spend, disappearance, or loss of its asset retires the offer; it never follows the asset into a new output. If an unrelated spend is later reorganized away, place the targeted offer again after chain state is verified. Recovery of the marketplace’s own pending settlement continues to use its existing signed transaction.
{ "bids": [{ "txid": "bbbb...bbbb", "vout": 0 }], "targets": [{ "scope": "collection", "collection": "rare-pepe", "series": 1 }], "expires_at": 1792000000}A target is {"scope": "asset", "asset": "..."} or a collection predicate over
series, artist, issued_year, min_supply_units, and max_supply_units.
Arbitrary trait name/value expressions are not part of this route. A request can
carry 20 slots and 500 targets, at most 1,000 slot/target combinations.
3. Authorize exact targets
Section titled “3. Authorize exact targets”A policy row alone is not immediately acceptable. To let named sellers finish without you, sign exact authorizations:
- Find targets:
GET /v1/offers/:id/authorization-targets(live listings your policy matches but has not authorized) orGET /v1/collections/:slug/offer-targets. Known unlisted outpoints work too. POST /v1/offers/:id/exact-authorizations/batchwith 1..100{utxo_txid, utxo_vout}targets. The batch is validated whole and stored all or nothing. Each target’s asset UTXO needs 3 confirmations; a younger one is refused with409 state_changed,retryable: true, and itsconfirmations/required_confirmations, so schedule the retry for when it is deep enough rather than polling.- Sign each returned buyer PSBT: input 0 only,
DEFAULT|ALL, thenPOST /v1/exact-offer-authorizations/:id/bidder-psbtwithpsbt_hex.
DEFAULT|ALL means the signature must commit to every input and output; on
Taproot both SIGHASH_DEFAULT (0x00) and SIGHASH_ALL (0x01) do. When the PSBT
input already declares a sighashType, sign with that value: most signing
libraries refuse a different one.
curl -sS -X POST "$API/v1/offers/$OFFER_ID/exact-authorizations/batch" \ -H 'Content-Type: application/json' -H "x-session-token: $TOKEN" \ -d '{"targets":[{"utxo_txid":"YOUR_ASSET_TXID","utxo_vout":0}]}'An authorization is a signed snapshot of that outpoint. A new or moved output needs a fresh authorization.
How the seller accepts
Section titled “How the seller accepts”- Find incoming offers:
GET /v1/address/:address/exact-offers(seller session), orGET /v1/asset-utxos/:txid/:vout/exact-offersfor one unit. GET /v1/exact-offer-authorizations/:id(seller session) returns the unsigned settlement template, the input-1 signing instruction, and the economics. When fee rates have risen it also returns a one-input, one-output CPFP child that pays the bump from the seller’s proceeds.- Judge the sale by
fee_adjustment.final_seller_proceeds_sats: what you keep after the child’s fee. The top-levelseller_proceeds_satsis the parent’s seller output before any CPFP.fee_adjustment.stateisready(no child),seller_cpfp_required(signcpfptoo), orunavailable(the bump cannot be paid from proceeds right now;reasonsays why). - Sign input 1 with
DEFAULT|ALL(and the CPFP child if present), thenPOST /v1/exact-offer-authorizations/:id/acceptwithpsbt_hexand optionalcpfp_psbt_hex. The server merges the buyer’s stored signature, journals, and broadcasts.
Status for either party: GET /v1/exact-offer-authorizations/:id/status. A seller
may instead answer with a private counter
(POST /v1/exact-offer-authorizations/:id/counter-preflight, then /counter); it
must beat the offer by at least 1,000 sats in 1,000-sat steps.
Cancel and release
Section titled “Cancel and release”| Action | Route |
|---|---|
| Cancel one policy | POST /v1/offers/:id/cancel |
| Cancel one placement’s policies | POST /v1/offer-placements/:id/cancel |
| Cancel one exact authorization | POST /v1/exact-offer-authorizations/:id/cancel |
| Release the funds on-chain | POST /v1/offer-slot-releases: an unsigned self-send of your slots back to you |
POST /v1/offer-slot-releases takes slots (1..20 {txid, vout}),
bidder_public_key (P2TR: the 32-byte x-only internal key; P2WPKH: the 33-byte
compressed key; it must own the slots), and an optional fee_rate. It stores
nothing. Sign signing.inputs_to_sign, finalize, check the txid against
expected_txid, and broadcast the bytes yourself or relay them with
POST /v1/transactions (raw_tx_hex). Once the spend is seen, every offer and
authorization on those slots retires.
Marketplace cancellation is not Bitcoin revocation. A signed authorization stays valid on Bitcoin until its slot is spent. Spending the slot (the release self-send) retires every offer and authorization on it.
Collection offers (funded_policy_offer_v1)
Section titled “Collection offers (funded_policy_offer_v1)”A collection, trait, or asset offer that any holder of a qualifying unit can fill, listed or merely prepared, without the buyer returning.
You sign a version-3, zero-fee funding parent. Its output 0 is a Taproot offer output worth exactly your price, whose script commits to the price, expiry, policy, and your delivery address. The parent also spends a small 330-sat market anchor that the market signs only at acceptance. Because it pays no fee, the parent cannot relay alone, so nothing appears on-chain until a seller accepts. The seller builds a child that spends your offer output and their asset UTXO, detaches the unit to you, and pays the package fee from their proceeds; the market signer co-signs it only while the offer is live and the unit matches the policy. Request, response, and signing schemas are in the API reference.
POST /v1/policy-offers/preflightwithbidder_public_key(P2TR: 32-byte x-only key; P2WPKH: 33-byte compressed key), optionaldelivery_address, and 1..100alternativesof{price_sats, expires_at, policy}. It selects confirmed, asset-free coins (or verifies explicitfunding), assigns an anchor, and returns one parent per alternative plus afund_policy_offerwallet request. It reserves nothing.- Sign every parent. The one top-level
signinginstruction (inputs_to_sign,sighash: "DEFAULT") applies to each alternative’spsbt_hex. Verify that the offer output equals your price and that the parents pay no network fee. POST /v1/policy-offerswithbidder_public_keyand the signedalternatives(psbt_hex,leaf_hex,policyeach). All or nothing; an identical retry answersalready_placed.
A policy has exactly these keys, with absent traits null:
{ "scope": "collection", "asset": null, "collection": "rare-pepe", "max_supply_units": null, "min_supply_units": null, "issued_year": null, "series": 1, "artist": null}expires_at must be between 10 minutes and 90 days from now. All alternatives
share one funding set, so at most one can fill. N units need N separate funding
sets. Limits: 8 funding inputs, 100 alternatives, 25 open funding sets per
address (409 open_offer_limit). 503 offer_capacity_full means offer capacity
is temporarily unavailable; retry with backoff.
Books: GET /v1/assets/:asset/policy-offers, GET /v1/collections/:slug/policy-offers.
Yours: GET /v1/address/:address/policy-offers.
Cancel
Section titled “Cancel”- Soft cancel, free and instant: sign the BIP-322 message
digirare policy-offer cancel <parent_txid>andPOST /v1/policy-offers/:id/cancelwithaddressandsignature. The market signer refuses that parent from then on. Your coins stay where they are. - Hard cancel, trustless:
POST /v1/policy-offers/:id/cancel-transaction(optionalfee_rate) returns an unsigned self-send of every funding input. Signsigning.inputs_to_sign, finalize, and broadcast it yourself or relay it withPOST /v1/transactions. It kills every alternative of that funding set on-chain. Once the spend is seen, those alternatives readinvalidinGET /v1/address/:address/policy-offers, even if you soft-cancelled first.
Expiry is enforced by the market signer’s clock, not by Bitcoin script: an expired parent is still valid Bitcoin until its funding or anchor is spent. Its coins are free again the moment it expires, as after a soft cancel: new offers, attaches, and purchases may spend them, and spending them kills the old parent.
Accept (seller)
Section titled “Accept (seller)”POST /v1/policy-accepts/preflightwithparent_txid, your one-unitasset_txid/asset_vout, and optionallyfee_rateandseller_min_net_sats. The quote showsseller_proceeds_sats,platform_fee_sats, andnetwork_fee_sats(the whole package). Withoutfee_ratethe package pays 20% above the next-block rate: a zero-fee parent and its child travel as a package, which pools were slower to mine at the plain rate.- Sign input 1 of the child and
POST /v1/policy-acceptswithaccept_idandpsbt_hex. 200means done;202means the journaled package is still relaying. PollGET /v1/policy-accepts/:idand never sign a new child while it issigned,journaled, orbroadcast.
The unit must be attached, confirmed, and indexed before it can accept. A
refusal from the market signer arrives as policy_offer_refused with a typed
refusal_code.
Accepting several
Section titled “Accepting several”Each acceptance is its own transaction and its own signature. To sell into many offers at once, pick one offer per funding slot and one per unit, then accept them in order:
- For exact offers,
POST /v1/offer-acceptance-selections/validatewith up to 20offer_idsfirst. It reserves nothing; it refuses a selection whose offers share a funding slot (409 duplicate_funding_slot, with theconflicts) or can no longer execute (offer_selection_unavailable). Collection offers carryfunding_slot_idtoo: never pick two that share one. - Accept each one as above. Re-quote a collection offer whose quote expired and sign it only if it still pays what you checked.
- Stop at the first refusal. Earlier acceptances stand; build a new selection for the rest.
Signatures stay private
Section titled “Signatures stay private”Every response carries an unsigned template, your own signature, or a broadcast transaction. The server merges the other party’s signature at completion and never serves it to you.