Recovery
Stop before doing more work if the wallet rejects, the session changes, a request times out, or storage fails. Earlier successful operations stay successful. Do not throw away your log and rerun the whole plan with new identifiers.
A timeout or 5xx does not prove a write failed. Reconcile first, then act.
What to check
Section titled “What to check”| Interrupted operation | Recovery |
|---|---|
| Listing preflight response lost | Repeat the exact request with the same idempotency_key, address, asset, outpoint, and price |
| Listing signature submitted, response lost | Read GET /v1/listing-preflights/:id. If completed, read the listing. Otherwise retry the saved signed bytes for that same preflight |
| Wallet rejected a listing | Keep completed items. Read the saved preflight before signing unfinished work; prepare again if it expired |
| Change an unsigned listing draft | POST /v1/listing-preflights/:id/price on the existing draft. Record the desired price first; read back the exact template before signing |
| Remove an unsigned listing draft | POST /v1/listing-preflights/:id/discard (owner). Retry a lost response with the same id. It releases the prepared output, not an attachment or an existing listing |
| Reprice response lost | Read managed listings. Check the current price and status before resubmitting saved bytes; do not overwrite a newer intentional reprice |
| Delist or cancel response lost | Read your listings and offers. A missing active listing does not tell sale, expiry, and cancellation apart; reconcile fill history before acting |
| Checkout completion response lost | Read GET /v1/fills/:id and GET /v1/address/:address/in-flight-purchases. Resubmitting the same signed PSBT is safe; it answers already_completed |
| Offer placement response lost | Read GET /v1/address/:address/offers and match the funding slot, collection, expiry, and placement id. POST /v1/offers is not generally idempotent |
| Exact authorization batch response lost | Repeat the same batch for the same offer id and exact target set; pending authorizations are reused |
| Buyer authorization signature submitted, response lost | Read GET /v1/exact-offer-authorizations/:id/status. If active, settling, or completed, do not sign again. If still awaiting the buyer, resubmit the saved PSBT |
| Collection offer placement response lost | Repeat the identical POST /v1/policy-offers with the same signed parents; a stored set answers already_placed. Check GET /v1/address/:address/policy-offers |
Collection offer acceptance lost, or 202 |
Poll GET /v1/policy-accepts/:id. Do not sign a new child while it is signed, journaled, or broadcast |
| Soft cancel response lost | Read GET /v1/address/:address/policy-offers; repeating the same signed cancel is harmless. To be certain on-chain, sign the cancel-transaction self-send |
| Self-relayed attach, relay uncertain | Rebroadcast the exact saved bytes with POST /v1/transactions; check with POST /v1/transactions/status. Never re-sign |
401 |
Get a fresh session for the same owner, then inspect state before resuming |
409 |
Re-read that order or outpoint. Never silently switch to another asset or funding output |
429 |
Wait retry_after_seconds (also sent as Retry-After), then reconcile before retrying a write |
| Anything uncertain about your own orders | Read GET /v1/address/:address/events (owner session): fills, delists, expiries, accepted and cancelled offers, and confirmations in one feed. See Events |
A ready preflight alone does not prove that an uncertain submission failed: the
request may still be in progress. Reconcile before editing. Preflight status reads
recover listing_id and listed_price_sats from an accepted listing even if
completion was interrupted.
Reprice previews check the expected old price, but the API has no atomic expected-version condition for repricing. Do not run two repricing agents against the same inventory and assume the preview is a lock.
Keep a log, and protect it
Section titled “Keep a log, and protect it”Write every preflight id, request body, and signed PSBT to disk before you submit it; that is what makes an uncertain submission recoverable. Use a durable, append-only log, and stop on an uncertain response until you reconcile its status.
That log is sensitive trade authorization, even though it is not a seed:
- Keep it out of version control; add your local log directory to
.gitignore. - Restrict it with filesystem permissions and never paste it into an issue.
- Never write session tokens or private keys into it.
- A truncated final line may mean the process stopped mid-write. Keep it and reconcile the previous request before continuing.
Cancellation is not signature erasure
Section titled “Cancellation is not signature erasure”Delist and cancel remove the marketplace’s authority to offer an order. They do not revoke a Bitcoin signature. Spending the signed input is what invalidates it:
| To make it impossible on-chain | Spend |
|---|---|
| A listing | The asset UTXO |
| An exact-target offer | The funding slot (POST /v1/offer-slot-releases) |
| A collection offer | Any funding input (POST /v1/policy-offers/:id/cancel-transaction) |
A settlement already in progress may own the input; cancellation then returns a conflict instead of interrupting a signed trade. Moving asset UTXOs, detaching, and consolidating funding are separate decisions involving real Bitcoin transactions, so the examples never do them automatically.