# DIGIRARE Marketplace API (full) ## Essentials - Live HTTP API prefix: https://api.digirare.com/v1 . Documentation: https://api.digirare.com/docs/ . Bitcoin mainnet. The public SDK is coming soon; use HTTP directly or generate a client from OpenAPI. Marketplace source code is currently private. - Public reads need no API key. Account actions use a 24-hour address session: POST /v1/auth/challenge, sign the returned message, POST /v1/auth/verify, then send x-session-token. Sessions do not sign transactions. - Success: {"result": ...}. Paged reads also include result_count and next_cursor. Errors: {"error": "...", "code": "..."}. Branch on code, not message text. - Keys stay local. Review the PSBT and sign only the specified inputs with the specified sighash. Return signed PSBT hex; never send private keys. - Trading signers: P2WPKH or P2TR. Detached delivery supports any Counterparty address family. A separate P2PKH, P2WPKH, or P2TR source may sign its own attach inputs. - BTC values are integer satoshis; raw asset quantities are decimal strings. Canonical asset IDs identify assets; asset_longname is display metadata. - Prices start at 5,000 sats. Asks are capped at 100 BTC; funded offers have no equivalent ask cap. The taker pays 2.5% rounded up, minimum 1,000 sats, plus the settlement network fee. - Buy: POST /v1/fills/request -> review and sign -> POST /v1/fills/complete with the buyer session. Quotes reserve nothing. A 409 checkout_changed requires a fresh quote and signature. Delivery defaults to detached; attached delivery is limited to one acquired unit at the buyer address. - List: POST /v1/listing-preflights -> follow next_action -> submit the signed listing. A prepared unit needs no attach transaction; otherwise sign and submit the attach first. Sign only listing.inputs_to_sign with SINGLE|ANYONECANPAY. - Offers: exact_offer_v1 authorizes named asset outpoints. funded_policy_offer_v1 covers any qualifying prepared unit during its lifetime. Alternatives sharing funding_slot_id represent one possible purchase, not independent liquidity. - Limits: quote/composition writes 20/minute per route/address, 120 per route/IP; signed completions 120/600; auth 60 per route/IP. Uncached reads share 300/1,800 address/IP budgets; recovery reads and account maintenance have separate budgets. See the limits guide for categories and exceptions. - On 429, honor Retry-After. A timeout or 5xx does not prove a write failed: read saved operation status before retrying or signing another transaction. - GET /v1/address/{address}/events requires the owner session. Poll the first page with overlap and deduplicate by event_id. GET /v1/events and GET /v1/analytics are public. - Cancellation removes an order from marketplace execution; Bitcoin revocation requires spending its signed input. Read the workflow guide before releasing funds. # Quickstart Source: https://api.digirare.com/docs/quickstart/ The DIGIRARE API provides market data and trading for Counterparty collectibles on Bitcoin. Public data needs no API key or wallet. Trading uses an address session and transactions signed by your wallet or local signer. ## Make your first request Requests use `https://api.digirare.com/v1` on Bitcoin mainnet. The examples below set `API` to the origin and include `/v1` in each request path. ```sh export API=https://api.digirare.com curl -sS "$API/v1/assets/RAREPEPE/book" ``` This reads an asset's order book. It does not create an order or spend bitcoin. The response wraps the book in `result`: | Field | What it contains | | --- | --- | | `result.asks` | Asking prices and the quantity available at each price | | `result.listings` | Individual listings, including seller, outpoint, and price | | `result.offers` | Offers any holder of the asset can fill | | `result.direct_offers` | Offers on one specific unit (`target_txid`, `target_vout`) | | `result.fills` | Recent trades | An asset can have many editions. Use its canonical Counterparty ID, such as `RAREPEPE` or a numeric asset ID, in requests. A subasset's display name (`asset_longname`) is not its API identifier. ## Explore the market ```sh curl -sS "$API/v1/collections" curl -sS "$API/v1/collections/rare-pepe/assets?limit=20" curl -sS "$API/v1/fills?collection=rare-pepe&limit=20" curl -sS "$API/v1/analytics" ``` See [Market data](/docs/guides/market-data/) for filtering, pagination, and the difference between offer counts and funded buying capacity. ## Connect a signer when you are ready to trade You need two separate capabilities: 1. **A session:** sign a login message to prove control of your address. Send the returned token in `x-session-token` on authenticated requests. 2. **A transaction signer:** review an unsigned PSBT, sign the specified inputs locally, and return the signed PSBT. A session token cannot sign or spend. There is no API-key application and no minimum balance to sign in. Follow [Authentication](/docs/authentication/) to create a session. Use a Native SegWit (`bc1q…`) or Taproot (`bc1p…`) trading address; your asset source and delivery address can be different. Private keys and seed phrases stay with your signer. ## Choose a workflow | You want to… | Start here | | --- | --- | | Buy available units at a maximum price | [Buying](/docs/guides/buying/) | | Sell a unit you own | [Listing](/docs/guides/listing/) | | Bid on specific units or a collection | [Offers](/docs/guides/offers/) | | Track your orders and confirmations | [Events](/docs/guides/events/) | | Resume after an interrupted trade | [Recovery](/docs/guides/recovery/) | | Check public SDK availability | [SDK — coming soon](/docs/guides/sdk/) | Each trading guide identifies the prerequisites, signing instructions, and submission steps. An unsigned quote does not reserve inventory. Review the price, fees, destination, and inputs before signing. ## Request conventions - Send JSON bodies with `Content-Type: application/json`. - BTC amounts are integer satoshis. Raw asset quantities are decimal strings. - Paged responses include `next_cursor`; pass it back unchanged as `cursor` with the same filters until it is `null`. - Errors contain `code` and `error`. Use `code` in application logic; the human-readable message can change. - On `429`, wait for `Retry-After`. After a write times out, check its status before submitting again. The [API reference](/docs/reference/) contains request and response schemas. Download [OpenAPI](/docs/openapi.json) for client generation, or use [llms.txt](/docs/llms.txt) and [llms-full.txt](/docs/llms-full.txt) with a coding assistant. --- # Market data Source: https://api.digirare.com/docs/guides/market-data/ All routes on this page are public. Set `$API` as in the [quickstart](/docs/quickstart/). No session or signing key is required. ## Browse the catalog | Route | Use | | --- | --- | | `GET /v1/collections` | Discover collections | | `GET /v1/collections/{slug}/assets` | Browse a collection's assets | | `GET /v1/artists` | Discover artists | | `GET /v1/assets/{asset}` | Read an asset's metadata | | `GET /v1/assets/{asset}/book` | Read listings, price levels, offers, and trades | Use the returned collection slug and canonical asset ID in later requests. Asset IDs and subasset display names are not interchangeable. ```sh curl -sS "$API/v1/collections/rare-pepe/assets?limit=20" curl -sS "$API/v1/assets/RAREPEPE/book" ``` ## Read prices and available quantity An asking price is a seller's requested price, not a completed sale. A floor price is the cheapest available listing. Use the book for current asking prices, and fills for trading activity. Offers sharing `funding_slot_id` can fund only one purchase. Deduplicate by that ID when calculating liquidity; eligible targets are not independent buying capacity. See [Offers](/docs/guides/offers/#capacity-is-not-eligibility). ## Activity and analytics ```sh curl -sS "$API/v1/events?kind=sales&limit=20" curl -sS "$API/v1/fills?collection=rare-pepe&limit=20" curl -sS "$API/v1/analytics" ``` `GET /v1/events` covers listings, offers, and sales. `GET /v1/fills` is the trade history. `GET /v1/analytics` provides aggregate market metrics, daily sales, and sampled market depth. Check each response's status and time fields when deciding what to count as settled volume. Read [Events](/docs/guides/events/) for feed semantics and deduplication. Your private account feed is separate and requires your address's session. ## Pagination Paged routes return `result`, `result_count`, and `next_cursor`. Follow the cursor until it is `null`; the first page is not necessarily the full result. Keep filters unchanged while paging. Start without a cursor when you change them. Limits and available filters are listed per endpoint in the [reference](/docs/reference/). Using standard JavaScript `fetch`: ```js let cursor; do { const url = new URL("https://api.digirare.com/v1/fills"); url.searchParams.set("limit", "50"); if (cursor) url.searchParams.set("cursor", cursor); const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}: stop and check the response before retrying`); const page = await response.json(); for (const fill of page.result) { // Store or display the fill. Deduplicate when polling overlapping pages. } cursor = page.next_cursor; } while (cursor); ``` ## Freshness Public reads can be cached, and confirmed Bitcoin transactions take time to appear in Counterparty's indexed balances. A book snapshot does not guarantee that a listing is still available. Request a fresh quote before signing; the API verifies availability again at submission. Reuse results while rendering a page, avoid polling each card separately, and honor `Retry-After` on `429`. See [Limits and errors](/docs/guides/limits-and-errors/) for request budgets and indexing delays. --- # Authentication Source: https://api.digirare.com/docs/authentication/ Public market data needs no authentication. To manage orders or complete a trade, sign a message with your Bitcoin address and exchange the signature for a session token. There are no API keys or minimum-balance login requirements. ## Create a session ### 1. Request a challenge Set `$API` as shown in the [quickstart](/docs/quickstart/). ```sh curl -sS -X POST "$API/v1/auth/challenge" \ -H 'Content-Type: application/json' \ -d '{"address":"YOUR_BITCOIN_ADDRESS"}' ``` Save `result.message` and `result.issued_at`. The challenge expires after 10 minutes. Sign the exact message, including its newlines, using your wallet or local signer. ### 2. Submit the signature ```sh curl -sS -X POST "$API/v1/auth/verify" \ -H 'Content-Type: application/json' \ -d '{ "address":"YOUR_BITCOIN_ADDRESS", "issued_at":1790000000, "signature":"YOUR_BASE64_MESSAGE_SIGNATURE" }' ``` Replace the example timestamp with the challenge's `issued_at`. Successful verification returns `result.token`, `result.address`, and `result.expires_at`. The token lasts **24 hours**. ### 3. Send the token ```sh export TOKEN='YOUR_SESSION_TOKEN' curl -sS "$API/v1/address/YOUR_BITCOIN_ADDRESS/events?limit=20" \ -H "x-session-token: $TOKEN" ``` When the token expires, authenticate again as the same address. If a trading request was interrupted, read its status before resuming. ## Message signing formats The default is **BIP-322**. A signer returning a BIP-137 recoverable signature must declare the format in the verification request: ```json { "verification": { "method": "BIP-137", "format": "legacy_recoverable" } } ``` P2PKH (`1…`) addresses also accept a classic 65-byte recoverable signature or a two-item signature/public-key stack. A wrong key, changed message, or expired challenge returns `401 signature_invalid`. Browser integrations with a compatible connect-time proof can use `POST /v1/auth/session`. That proof must be at most five minutes old and bound to an allowed site origin. Use the challenge flow for bots and other integrations. ## SDK availability The public TypeScript SDK is [coming soon](/docs/guides/sdk/). The HTTP flow above works today with your wallet or local message signer. ## Session permissions and revocation A session authorizes private reads and account actions for **one address**, including delisting, cancelling orders, and eligible artist-profile edits. It does not authorize spending: trades still require Bitcoin transaction signatures. Tokens have no read-only or per-action scopes and cannot be revoked individually. Disconnecting removes the local token; it does not invalidate copies. Creating a new session does not invalidate earlier sessions. For a bot, use a dedicated trading address, keep its token separate from its signer, and renew the session as needed. Public analytics does not need a token. Do not share a portfolio token with a service that only needs public data. ## Three address roles The address that signs a trade does not have to store your collection. | Role | Purpose | Supported address families | | --- | --- | --- | | **Signing** | Funds purchases and signs listings, offers, and acceptances | P2WPKH (`bc1q…`), P2TR (`bc1p…`) | | **Delivery** | Receives a detached purchased balance | Any family supported by Counterparty | | **Source** | Supplies a balance when preparing a unit for sale | P2PKH (`1…`), P2WPKH, P2TR | Authentication and transaction signing are separate checks. An address can authenticate yet be unsupported for a particular trading role; that request returns `400 unsupported_address_type`. ### Choose a delivery address - Checkout uses `delivery_address`, defaulting to `buyer_address`. - Exact offers use `receive_address`. - Collection-offer preflights use `delivery_address`, defaulting to the session address. Detached delivery can go to a legacy address. Attached delivery returns the asset UTXO to the trading address; see [Buying](/docs/guides/buying/#delivery). ### Sell from a different source address `POST /v1/listing-preflights` and `POST /v1/attach-templates` accept `asset_source` separately from the seller or `utxo_owner`. The source signs its own attach inputs; the prepared unit belongs to the SegWit seller, who signs the listing. Use the seller's session for this workflow. With a P2PKH source, signing can change the attach transaction ID. Read the final asset outpoint from the preflight after submission rather than predicting it from the unsigned transaction. --- # Fees and prices Source: https://api.digirare.com/docs/fees-and-prices/ | Rule | Value | | --- | --- | | Marketplace fee | 2.5% of the price, rounded **up**, minimum **1,000 sats** | | Who pays it | The **taker**: whoever completes the trade | | Minimum price | **5,000 sats** for every listing, reprice, counter, and offer | | Maximum ask | **100 BTC** (10,000,000,000 sats); funded offers do not have this ask cap | | Units | Integer satoshis everywhere; large raw asset quantities are decimal strings | A price below the minimum is refused with `price_below_minimum`; the error body carries `minimum_price_sats`. Compute the fee with integer arithmetic: ```ts const fee = Math.max(1_000, Number((BigInt(priceSats) * 250n + 9_999n) / 10_000n)); // 5,000 -> 1,000 40,000 -> 1,000 100,000 -> 2,500 100,001 -> 2,501 ``` ## Buying a listing The buyer is the taker. The buyer pays the listed prices, the marketplace fee, and the network fee; each seller receives exactly their listed price. The quote from `POST /v1/fills/request` states every part before you sign: ```json { "subtotal_sats": 100000, "platform_fee_sats": 2500, "network_fee_sats": 1240, "total_sats": 103740 } ``` For a multi-item cart the fee is computed on the cart subtotal. ## Making an offer Offer funding covers the price and, for attached delivery, the retained asset UTXO value. The accepting seller pays the marketplace and settlement fees. - A detached-delivery funding slot is worth exactly the price. - An attached-delivery slot adds the 330-sat asset UTXO the buyer keeps. - A collection offer's Taproot offer output is worth the price; its funding parent pays no network fee (the seller's acceptance pays for the package). Creating exact-value funding slots with `POST /v1/offer-funding-templates` is an ordinary Bitcoin transaction, so that one step costs you a network fee. ## Accepting an offer The seller is the taker. The marketplace fee and the settlement's network fee (for a collection offer, the parent-plus-child package fee) come out of the seller's proceeds. Every accept quote states `seller_proceeds_sats`, `platform_fee_sats`, and `network_fee_sats` before anything is signed. Collection offer acceptance also takes an optional `seller_min_net_sats` floor. A few funding slots created under an older bidder-pays policy carry a prefunded fee (`prefunded_fee_sats`). The seller then pays only the remainder. For example: price 100,000, asset UTXO 330, 3 sat/vB over a 600 vB package. Fee 2,500, network 1,800, seller receives `100,000 + 330 - 2,500 - 1,800 = 96,030` sats. At the 5,000-sat minimum price and a high fee rate, the proceeds can go negative, and the accept is refused. ## Verify the quoted transaction - The **seller** is derived from the listed UTXO's scriptPubKey. - A **listing price** is the verified seller payment output in the signed PSBT minus the listed asset UTXO's value. - An **offer price** is the exact value of its funding output. - A buyer pays exactly what the stored template says, or completion is refused. Use the returned economics for review, and verify that the PSBT matches your intended asset, payment, fees, and destination before signing. ## Network fee rates Composing routes take an optional `fee_rate` in sat/vB, fractional allowed, from 0.1 to 500. Omit it and the server picks the current network rate. `GET /v1/fee-rate` returns the rates the server sees. --- # Listing Source: https://api.digirare.com/docs/guides/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](/docs/authentication/) 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 1. Read prepared inventory. It needs no session and returns exact outpoints. 2. `POST /v1/listing-preflights` with an `idempotency_key`, `seller`, `asset`, `asset_utxo: {txid, vout}`, and `price_sats`. 3. Require `mode: "reuse"`, `status: "ready"`, and the same outpoint and price. 4. Sign `listing.psbt_hex`: only `listing.inputs_to_sign`, with `SINGLE|ANYONECANPAY`. Do not sign inputs outside that instruction. 5. `POST /v1/listing-preflights/:id/listing` with `psbt_hex` and `expires_at` (Unix seconds, or `null` for no marketplace expiry). ```sh 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 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. 1. `GET /v1/address/:address/inventory/attachable` for detached balances. 2. `POST /v1/attach-templates` with `utxo_owner`, `asset`, and optionally `asset_source`, `fee_rate`, `exclude`, `in_flight`, and `funding`. It returns one verified one-unit attach PSBT, its signer groups, the change output, and a fee quote. It writes nothing and reserves nothing. 3. Sign it, then relay the finalized bytes with `POST /v1/transactions` (`raw_tx_hex`). 4. Track it yourself. `POST /v1/transactions/status` checks up to 25 txids; `final` means 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, and `asset_utxo` once 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 `change` object (`txid`, `vout`, `value_sats`, `script_pub_key_hex`) back as `funding: [change]`. Without explicit `funding` the server selects only confirmed coins, and answers `422` with `reason: "no_funding"` and a `pending_confirmation` count 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 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: 1. `POST /v1/attach-templates/speed-up` with `owner` (your session address), `txid` (the attach), and optionally `fee_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. 2. Check `result.child`: one input (the attach's `change`, never output 0, the asset UTXO), one output back to you worth `final_change_sats`, transaction version 2, locktime 0. `fees.package_fee_rate` is what the attach, its unconfirmed ancestors, and the child pay together. 3. Sign input 0 (`DEFAULT|ALL`), finalize, and relay it with `POST /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 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: ```json "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`, poll `GET /v1/listing-preflights/:id` until `status` is `ready`, 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 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 `POST /v1/listings/:id/delist` with the owner's session. No signature needed. ```sh 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 `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. :::caution[Delisting is not Bitcoin revocation] A listing signature stays valid on Bitcoin until its asset UTXO is spent. Delist removes the marketplace's authority to offer it; the market seals stored signatures and wipes them on delist, but to be certain on-chain, move the asset UTXO. Delist first, then move. A move that is still unconfirmed can be outbid by a purchase that was already signed. The market refuses to complete a sale once it sees the unit moved, but another node may see your move before ours does. ::: ## 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. --- # Buying Source: https://api.digirare.com/docs/guides/buying/ Buy one or more available units by requesting a quote, signing it, and submitting the signed PSBT. Before starting, create a [session](/docs/authentication/) for your buying address and use an HTTP client. Your signer must support PSBT signing for the returned input indexes and sighash. Addresses, signatures, and transaction IDs shown as placeholders must be replaced with your own values. ## Request, review, and complete Check the returned quote before signing: `total_sats` must equal `subtotal_sats` plus `platform_fee_sats` and `network_fee_sats`; `signing.sighash` must be `ALL`. Verify the actual PSBT against your intended purchase, sign only `signing.inputs_to_sign`, then submit the signed PSBT with the quote’s `fill_id`. ```sh curl -s -X POST "$API/v1/fills/request" -H 'content-type: application/json' -d '{ "items": [{ "asset": "RAREPEPE", "quantity": 1, "max_price_sats": 120000 }], "buyer_address": "bc1q..." }' curl -s -X POST "$API/v1/fills/complete" -H 'content-type: application/json' \ -H "x-session-token: $TOKEN" \ -d '{"fill_id":"...","psbt_hex":"70736274ff..."}' ``` ## Choose quantity and maximum price A cart line is `{asset, quantity, max_price_sats}`, not a listing id. The API resolves each line to the cheapest listings at or under your `max_price_sats` when it builds the quote. `max_price_sats` is a per-unit ceiling and is your slippage tolerance. A cart holds at most 10 lines and 20 units. For one particular unit, add `target_outpoint: {txid, vout}` to the line and set `quantity: 1`. The API then buys only that outpoint, even if another unit is cheaper. It never substitutes another unit. A missing, unavailable, or over-budget target returns `409 target_unavailable`; a stale signed quote still returns `checkout_changed`. Refreshing keeps the same target and price ceiling. There is still at most one line per asset. The quote lists exactly what you are buying in `settlement_items` (listing id, seller, outpoint, price) and which coins fund it in `selected_outpoints`. ## Quotes reserve nothing A quote is a short-lived PSBT snapshot (about three minutes). Other buyers can hold overlapping quotes at the same time. Only `POST /v1/fills/complete`, after it verifies your signature byte-for-byte against the stored template, claims the listings. **The first valid signature returned wins**, not the first quote. `POST /v1/fills/request` needs no session for a public cart. Completing it requires the buyer's session and a `SIGHASH_ALL` signature on the funding inputs. Sign only `signing.inputs_to_sign`. Your funding inputs must be free of Counterparty assets; the API proves that before quoting. ## When the book moves: `checkout_changed` If a listing sold, was repriced or delisted, or its UTXO moved, completion answers `409` with code `checkout_changed`. The body is written against your original intent: ```json { "error": "checkout changed — review and sign a fresh transaction", "code": "checkout_changed", "can_fulfill": true, "requires_new_signature": true, "previous_subtotal_sats": 200000, "current_subtotal_sats": 205000, "previous_platform_fee_sats": 5000, "current_platform_fee_sats": 5125, "changes": [ { "asset": "RAREPEPE", "requested": 2, "available": 2, "max_price_sats": 120000, "current_subtotal_sats": 205000 } ] } ``` Request a new quote and sign again. Never resubmit the old PSBT, and never have your bot silently accept a smaller quantity or a different asset: decide explicitly, then sign the new template. Other refusals worth handling: `insufficient_funds` (the body carries `need_sats`, `have_sats`, `shortfall_sats`), `signature_invalid`, `template_mismatch`, and `expired`. ## Delivery By default the purchased balance is **detached** to an address: output 0 of the checkout is an encrypted Counterparty detach `OP_RETURN`, and one detach message delivers every unit in the cart. You receive an ordinary address balance, not an asset UTXO. - `delivery_address` sets where it goes. It can be any Counterparty address family, legacy `1...` included. It defaults to `buyer_address`. - `buyer_address` itself is the signing address that funds the purchase, so it must be P2TR or P2WPKH. `quote.delivery` echoes `mode` and `address`. Its `defaulted` flag is `true` when you omitted `delivery_mode` and received the detached default; it says nothing about the address, so check `delivery.address` against the destination you meant. For a single acquired unit, `delivery_mode: "attached"` keeps the asset on a new UTXO at the buyer's trading address. This requires exactly one acquired asset and cannot use a different delivery address. The quote exposes the retained BTC as `delivery.utxo_value_sats`; include it when showing the total wallet debit. It is retained value, not a fee. ## After completion `completeFill` returns the broadcast `txid` (and `already_completed: true` when you repeat a completion that already happened). The server journals the exact transaction before it broadcasts and rebroadcasts it if needed. | Route | Use | | --- | --- | | `GET /v1/fills/:id` | Buyer-session status of one checkout, for a lost response | | `GET /v1/address/:address/in-flight-purchases` | Your journaled, unconfirmed purchases | | `GET /v1/fills` | Public recent fills (`collection`, `limit`, `cursor`) | ## Private counters A seller can answer your exact offer with a private counter. Fill it through the same checkout with `counter_id` in place of `items`: `POST /v1/fills/request` `{"counter_id": "...", "buyer_address": "..."}`. Only the addressed buyer can see or fill it. List yours with `GET /v1/address/:address/counters`. ## Mempool races Between broadcast and confirmation, a purchase is visible in the mempool like any Bitcoin transaction. - **Listings.** The seller's listing signature is never served; only the market builds purchases from it. Once your purchase is broadcast, though, that signature is public, and someone could build a competing purchase that pays the seller the same price with a higher fee. You would lose the race, not money: your coins are simply not spent. Checkout broadcasts at the next-block rate so the window is short. - **Exact offers and collection offers** are fully committed transactions; no third party can reuse their signatures. - **A bidder can back out** of an accepted but unconfirmed offer by double-spending their own funding with a higher fee. The sale then fails and no asset moves. --- # Offers Source: https://api.digirare.com/docs/guides/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](/docs/authentication/), 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 A **funding slot** (`funding_slot_id = ":"`) 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 ### 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-preflights` with `txid`, `vout`. The answer confirms it is unspent, asset-free, and yours. - Otherwise `POST /v1/offer-funding-templates` with `price_sats` and `slot_count` (1..20). Sign and finalize the self-send locally, then `POST /v1/funded-offers` with `funding_tx_hex`, the slot `bids`, `targets`, and `expires_at`. This **broadcasts a Bitcoin transaction** and places the policies. ### 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. ```json { "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 A policy row alone is not immediately acceptable. To let named sellers finish without you, sign exact authorizations: 1. Find targets: `GET /v1/offers/:id/authorization-targets` (live listings your policy matches but has not authorized) or `GET /v1/collections/:slug/offer-targets`. Known unlisted outpoints work too. 2. `POST /v1/offers/:id/exact-authorizations/batch` with 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 with `409 state_changed`, `retryable: true`, and its `confirmations` / `required_confirmations`, so schedule the retry for when it is deep enough rather than polling. 3. Sign each returned buyer PSBT: input 0 only, `DEFAULT|ALL`, then `POST /v1/exact-offer-authorizations/:id/bidder-psbt` with `psbt_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. ```sh 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 - Find incoming offers: `GET /v1/address/:address/exact-offers` (seller session), or `GET /v1/asset-utxos/:txid/:vout/exact-offers` for 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-level `seller_proceeds_sats` is the parent's seller output before any CPFP. `fee_adjustment.state` is `ready` (no child), `seller_cpfp_required` (sign `cpfp` too), or `unavailable` (the bump cannot be paid from proceeds right now; `reason` says why). - Sign input 1 with `DEFAULT|ALL` (and the CPFP child if present), then `POST /v1/exact-offer-authorizations/:id/accept` with `psbt_hex` and optional `cpfp_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 | 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`) 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](/docs/reference/). ### Place 1. `POST /v1/policy-offers/preflight` with `bidder_public_key` (P2TR: 32-byte x-only key; P2WPKH: 33-byte compressed key), optional `delivery_address`, and 1..100 `alternatives` of `{price_sats, expires_at, policy}`. It selects confirmed, asset-free coins (or verifies explicit `funding`), assigns an anchor, and returns one parent per alternative plus a `fund_policy_offer` wallet request. It reserves nothing. 2. Sign every parent. The one top-level `signing` instruction (`inputs_to_sign`, `sighash: "DEFAULT"`) applies to each alternative's `psbt_hex`. Verify that the offer output equals your price and that the parents pay no network fee. 3. `POST /v1/policy-offers` with `bidder_public_key` and the signed `alternatives` (`psbt_hex`, `leaf_hex`, `policy` each). All or nothing; an identical retry answers `already_placed`. A policy has exactly these keys, with absent traits `null`: ```json { "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 - **Soft cancel**, free and instant: sign the BIP-322 message `digirare policy-offer cancel ` and `POST /v1/policy-offers/:id/cancel` with `address` and `signature`. 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` (optional `fee_rate`) returns an unsigned self-send of every funding input. Sign `signing.inputs_to_sign`, finalize, and broadcast it yourself or relay it with `POST /v1/transactions`. It kills every alternative of that funding set on-chain. Once the spend is seen, those alternatives read `invalid` in `GET /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) 1. `POST /v1/policy-accepts/preflight` with `parent_txid`, your one-unit `asset_txid`/`asset_vout`, and optionally `fee_rate` and `seller_min_net_sats`. The quote shows `seller_proceeds_sats`, `platform_fee_sats`, and `network_fee_sats` (the whole package). Without `fee_rate` the 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. 2. Sign input 1 of the child and `POST /v1/policy-accepts` with `accept_id` and `psbt_hex`. 3. `200` means done; `202` means the journaled package is still relaying. Poll `GET /v1/policy-accepts/:id` and never sign a new child while it is `signed`, `journaled`, or `broadcast`. 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 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: 1. For exact offers, `POST /v1/offer-acceptance-selections/validate` with up to 20 `offer_ids` first. It reserves nothing; it refuses a selection whose offers share a funding slot (`409 duplicate_funding_slot`, with the `conflicts`) or can no longer execute (`offer_selection_unavailable`). Collection offers carry `funding_slot_id` too: never pick two that share one. 2. Accept each one as above. Re-quote a collection offer whose quote expired and sign it only if it still pays what you checked. 3. Stop at the first refusal. Earlier acceptances stand; build a new selection for the rest. ## 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. --- # Events Source: https://api.digirare.com/docs/guides/events/ `GET /v1/address/:address/events` (owner session) is one newest-first stream of what happened to your orders, so a bot does not have to poll the listing, offer, collection-offer, and purchase histories separately. ```sh curl -s "$API/v1/address/bc1q.../events?limit=50" -H "x-session-token: $TOKEN" ``` The JSON response contains `result` and `next_cursor`. | Kind | Role | When | | --- | --- | --- | | `listing_filled`, `listing_delisted`, `listing_expired`, `listing_invalidated` | seller | A listing ended | | `offer_accepted`, `offer_cancelled`, `offer_expired`, `offer_retired`, `offer_consumed_by_counter` | bidder | An exact-target offer ended or lapsed | | `exact_authorization_used`, `exact_authorization_retired` | bidder | An exact authorization settled, or was cancelled or invalidated | | `collection_offer_filled`, `collection_offer_cancelled`, `collection_offer_expired`, `collection_offer_retired` | bidder | A collection offer alternative ended or lapsed (`retired` covers superseded siblings) | | `purchase_confirmed`, `sale_confirmed` | buyer / seller | The market saw the settlement confirm | Each event carries `event_id`, `kind`, `at`, `role`, the subject's current `status`, `asset` / `collection`, `price_sats`, the settlement `txid` when there is one, and whichever of `listing_id`, `offer_id`, `authorization_id`, `parent_txid`, `funding_slot_id`, and `asset_utxo` apply. Never a PSBT, a signature, or a raw transaction. ## Polling - Poll the first page and **dedupe on `event_id`**. Re-read a short overlap (for example the last minute) rather than stopping exactly at the last id you saw: several events can share one second. - Follow `next_cursor` only to catch up on older events. The cursor is opaque and valid only for this route. - `at` is when the market **observed** the event. A confirmation is stamped when the market first saw it in a block, not with the block time, so new events arrive at the head. An expiry is derived at read time and carries its `expires_at`, the moment it became true. - A cancel or delist you made yourself appears too: the feed is what happened, not only what others did. ## Things to know - `purchase_confirmed` belongs to the fill's buyer, which for offers is the delivery address. If your offers deliver elsewhere, `offer_accepted`, `exact_authorization_used`, and `collection_offer_filled` still carry the settlement `txid`. - An event reflects its row's current state. If a reorg undoes a confirmation, its `*_confirmed` event disappears until it confirms again. - An on-chain spend supersedes a marketplace cancel for collection offers: a soft-cancelled offer whose funding you then spend with the hard cancel moves from `collection_offer_cancelled` to `collection_offer_retired` (status `invalid`), a new `event_id`. A cancelled exact offer whose slot you release stays `offer_cancelled`. - An exact offer accepted by a seller shows `offer_accepted` once the market sees its funding slot spent, usually within a watch cycle of the settlement. - Another address's feed answers `403 forbidden`; no session answers `401`. ## The public market feed `GET /v1/events` (no session) is the whole market moving, newest first: what the [activity page](https://digirare.com/activity) shows. `kind=sales`, `kind=listings`, or `kind=offers` narrows it to one family. ```sh curl -sS "$API/v1/events?kind=offers&limit=50" ``` | Kind | When | | --- | --- | | `sale` | A listing was bought or an offer accepted (the `/v1/fills` tape) | | `listed`, `delisted` | A public listing was placed or ended (`reason`: `owner`, `expired`, `invalid`) | | `offer_placed`, `offer_ended` | An asset offer was placed or ended | | `collection_offer_placed`, `collection_offer_ended` | A collection offer was placed or ended (`reason`: `cancelled`, `expired`, `invalid`, `superseded`, `filled`) | A filled listing or offer has no separate ended event: its sale is the event. Each event carries `event_id`, `kind`, `at`, `reason`, `asset` / `asset_longname` / `collection`, `price_sats`, `seller` / `buyer` / `bidder`, and on sales the `txid`, `fill_kind`, and `confirmed_at`. Private counters, PSBTs and signatures never appear. Responses are edge-cached for 15 seconds. --- # Limits and errors Source: https://api.digirare.com/docs/guides/limits-and-errors/ ## Error envelope API-generated failures use the same shape, including unknown routes and unsupported methods. Branch on `code`; the `error` text is for humans and may change. A proxy or Cloudflare rejection before the request reaches the API can have a different format. ```json { "error": "too many requests; retry shortly", "code": "rate_limited" } ``` Some errors add fields (for example `retryable: true`, `minimum_price_sats`, or `shortfall_sats`). Read the HTTP status and JSON `code` together. For example, `409 checkout_changed` requires a fresh quote and signature; retrying the old PSBT does not resolve it. Preserve additional response fields for diagnosis and recovery. ## HTTP clients Use a descriptive `User-Agent` for server-side integrations. An edge rejection can return HTML or a non-API error before your request reaches the service, so check the status and content type before parsing JSON. ## Limits | Boundary | Limit | | --- | --- | | Expensive writes, with a session | Exactly 20 in any 60 seconds per route and session address, then `429 rate_limited` | | Expensive writes, anonymous | Exactly 20 in any 60 seconds per route and IP | | Expensive writes, per-IP ceiling | Exactly 120 in any 60 seconds per route and IP, across every session | | Signed completions | Exactly 120 in any 60 seconds per route and session address (IP when anonymous), under a 600 per-IP ceiling | | Sign-in (`/v1/auth/*`) | Exactly 60 in any 60 seconds per route and IP | | Uncached reads | 300 in any 60 seconds per session address (IP when anonymous), with an 1,800 per-IP ceiling; one shared budget across paths and filters | | Recovery reads | Separate 300/1,800 budget for operation status and recovery inboxes | | Account maintenance | Separate 120/600 budget shared by delist, offer/placement/authorization cancellation, draft discard/price and cancellation review | | Artist profile edit/check | 6 per address, 30 per IP in any 60 seconds; shared by both actions | | JSON request body | 64 KiB on ordinary write routes | | Checkout cart | 10 lines, 20 units | | Exact-offer placement | 20 funding slots, 500 targets, 1,000 slot/target combinations | | Exact authorization batch | 100 asset outpoints | | Collection offer | 100 alternatives, 8 funding inputs, 25 open funding sets per address | | Transaction status batch | 25 txids | | Paged histories | At most 100 rows per page; follow `next_cursor` | "Expensive writes" are the routes that compose or verify against the chain before answering: listing and attach preflights, checkout quotes, offer funding and placement, exact authorizations, counters, collection-offer preflight/placement and acceptance preflight, listing batches, reprice preflights, offer-selection validation, and transaction relay. Each route has its own bucket, so one workflow cannot starve another. Signed submissions have a separate budget from quote creation. Send a current `x-session-token` even on routes where it is optional, so the address budget applies. The per-IP ceiling still applies across all sessions on that IP. Read budgets are shared across their routes and filters. Public cache hits do not consume read capacity; changing a cursor or filter does not create a new budget. Recovery and cancellation have separate budgets so routine polling does not use their capacity. On `429 rate_limited`, wait for `retry_after_seconds` or the matching `Retry-After` header before trying again. A rejected request does not count against the window. Do not retry an uncertain write until you have checked its status. ```json { "error": "too many requests; retry shortly", "code": "rate_limited", "retry_after_seconds": 37 } ``` Paged reads return `{result, result_count, next_cursor}`. Pass `cursor` back unchanged; a cursor is valid only with the filters it was issued under. ## Error codes | Code | Typical status | Meaning | | --- | --- | --- | | `invalid_request` | 400, 413 | Malformed or out-of-range input | | `unsupported_address_type` | 400 | A valid address in a role its family cannot play, usually a legacy address asked to sign. See [Authentication](/docs/authentication/#three-address-roles) | | `unauthenticated` | 401 | Missing, expired, or wrong-address session | | `forbidden` | 403 | Authenticated, but not the owner | | `not_found` | 404 | No such resource (private resources also answer this to non-owners) | | `method_not_allowed` | 405 | The path exists, but does not support this HTTP method; `Allow` lists supported methods | | `state_changed` | 409 | The order or outpoint changed under you | | `expired` | 410 | The quote, preflight, or counter has expired | | `invalid_transaction` | 422 | The PSBT or transaction failed verification | | `signature_invalid` | 401, 422 | A login proof or transaction signature did not verify | | `template_mismatch` | 422, 409 | The signed PSBT differs from the stored template | | `checkout_changed` | 409 | The cart can no longer settle as quoted. See [Buying](/docs/guides/buying/#when-the-book-moves-checkout_changed) | | `insufficient_funds` | 422 | Asset-free BTC cannot cover the quote; body has `need_sats`, `have_sats`, `shortfall_sats` | | `insufficient_asset_balance` | 422, 409 | The balance cannot cover an attach or its XCP fee; body has `asset`, `held_raw`, `committed_raw`, `needed_raw`, `purpose` | | `price_below_minimum` | 422 | Below 5,000 sats; body has `minimum_price_sats` | | `listing_price_above_maximum` | 422 | Ask above 100 BTC; body has `maximum_price_sats`. Funded offers have no equivalent ask cap | | `counter_price_invalid` | 422 | A counter that does not beat the offer in 1,000-sat steps; body has `minimum_counter_sats` | | `self_trade` | 409 | The same address would be buyer and seller: checkout skips your own listings (and answers this, with `own_listings`, when only yours are left), and you cannot offer on or sell into your own offer | | `duplicate_funding_slot` | 409 | A bulk selection names two offers backed by the same funding slot | | `offer_selection_unavailable` | 404, 409 | A selected offer can no longer be accepted | | `attached_delivery_single_item_only` | 422 | Attached delivery was requested for more than one item | | `mempool_rejected` | 422 | A node refused the transaction on policy that retrying cannot fix; body has `mempool_policy` (`fee_floor`: re-quote at a higher `fee_rate`; `chain_too_long` or `truc`: wait for a confirmation) and, on a completion, `claims_released` | | `conflict_spend` | 409 | An input was already spent by another transaction | | `broadcast_unknown` | 502 | The broadcast outcome is uncertain. Reconcile; do not re-sign | | `offer_capacity_full` | 503 | No collection-offer anchor is free right now; retry shortly | | `open_offer_limit` | 409 | The address already has 25 open collection-offer funding sets | | `policy_offer_refused` | 422, 409 | The market signer or protocol verifier refused; body has `refusal_code` | | `rate_limited` | 429 | Slow down | | `upstream_unavailable` | 502 or 503 | Bitcoin or Counterparty backend unavailable, often `retryable: true` | | `service_unavailable` | 503 | A required dependency is unavailable | | `internal_error` | 500 | Unexpected failure | Statuses are the usual ones; a route can differ, so check its entry in the [API reference](/docs/reference/). The client adds one code of its own, `invalid_response`, when a response is not the expected envelope. ## After a block: give it a few minutes A confirmation is not instantly visible everywhere. After a block that touches your coins or units: 1. Counterparty has to see the block and parse it (usually well under a minute). 2. The marketplace's indexer reads it on its next run; it runs every 2 minutes. 3. Public reads (books, collections, activity) are edge-cached for up to 30 seconds, and `/inventory/attachable` for 10. The cache key holds only the query parameters a route reads, so an extra parameter such as a cache-buster is ignored and does not force a fresh read. Session-owner reads and `/inventory/prepared` are never cached. So expect **about 2 to 3 minutes** between a block and the market reflecting it: a delivered unit appearing in `/inventory/attachable`, a new attach appearing in `/inventory/prepared`, a listing preflight moving from `awaiting_indexer` to `ready`, a sold or delisted ask leaving the public book. Your own writes answer at once; what lags is the market's view of the chain. If you check immediately and something is missing, that is usually this window, not a failure. A few refusals look like "you don't have it" while it lasts, for example an attach template answering `insufficient_asset_balance` with `held_raw: "0"` right after a unit was delivered to you. Wait a couple of minutes and retry the balance read; don't re-sign or re-broadcast anything. Use the saved transaction or operation status to distinguish a pending transaction from a balance that has not been indexed yet. Separate from this window, exact offers need the asset UTXO at **3 confirmations** (about 30 minutes), and a zero-fee collection-offer sale can take a few blocks to confirm; see [Offers](/docs/guides/offers/). ## Retry guidance **Reads are safe to retry. Writes are not blindly retryable.** | Response | What to do | | --- | --- | | `401` | Get a fresh session for the same address, then re-read state before resuming | | `409` | Re-read that order or outpoint. Never silently switch to another asset or funding output | | `429` | Wait `retry_after_seconds` (the `Retry-After` header carries the same value), then reconcile before retrying a write | | `503 offer_capacity_full` | Retry after a short wait | | `422` from `/v1/attach-templates` with `reason: no_funding` and `retryable: true` | Your BTC exists but is still confirming: retry after the next block, or chain on your previous attach's `change` by passing it as `funding` | | `503`/`502` during completion | Read the saved operation status first. Request a fresh quote only after confirming the old operation cannot complete | | Timeout or `5xx` | Does **not** prove a write failed. Reconcile first, using [Recovery](/docs/guides/recovery/) | Repeat a write only when it is idempotent: a listing preflight with the same `idempotency_key`, an identical `POST /v1/policy-offers`, or re-submitting the same signed bytes. Quotes never reserve inventory, so a stale quote fails with `409`; request a new one and sign again rather than retrying old bytes. Pace requests to stay within each route's budget. Stop after an uncertain write and reconcile its status before continuing. --- # Recovery Source: https://api.digirare.com/docs/guides/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 | 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](/docs/guides/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 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 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. --- # SDK — coming soon Source: https://api.digirare.com/docs/guides/sdk/ The DIGIRARE marketplace and HTTP API are live on Bitcoin mainnet. A public TypeScript SDK and packaged examples are **coming soon**. Installation instructions will appear here when they are available. The marketplace source code is currently private. You do not need repository access or an SDK to use the API. ## Integrate today - Follow the [quickstart](/docs/quickstart/) to read public market data with any HTTP client. - Use [Authentication](/docs/authentication/) to create an address session for account actions. - Follow the [listing](/docs/guides/listing/), [buying](/docs/guides/buying/), and [offer](/docs/guides/offers/) guides to compose, sign, and submit trades. - Download [OpenAPI](/docs/openapi.json) for request and response schemas or client generation. Requests use `https://api.digirare.com/v1`. Public reads need no API key. Authenticated requests use `x-session-token`; transaction signatures are separate. Your wallet or local signer must review the PSBT and sign only the requested inputs and sighashes. Private keys stay with you. ## Compatibility Breaking HTTP changes will use a new major path. Additive fields and endpoints stay within `/v1`. Treat response objects as extensible, handle unknown error codes, and reject transaction protocol versions your signer does not support. After an interrupted write, follow the [recovery guide](/docs/guides/recovery/) before submitting or signing again. --- # Glossary Source: https://api.digirare.com/docs/glossary/ ## Asset (asset id) The canonical Counterparty asset id, named (`RAREPEPE`) or numeric. Every route keys on it. A subasset's longname (`asset_longname`) is display metadata only. ## Edition Most assets here have many units (RAREPEPE has 300), so an asset page is an order book, not a single item. Trades move one unit per asset UTXO. ## Raw quantity An integer count of the smallest unit, sent as a decimal string. One indivisible unit is `"1"`; one divisible unit is `"100000000"`. ## Asset UTXO A Bitcoin output that carries an attached Counterparty balance. Every listing sells exactly one unit on its own asset UTXO, identified by `txid` and `vout`. Typically 330 sats. ## Attach A Counterparty transaction that moves units from an address balance onto an asset UTXO. It costs a Bitcoin network fee and may cost an XCP fee, which stays a quote until the attach is parsed. ## Prepared unit One unit already attached on its own asset UTXO, confirmed and indexed, so it can be listed without broadcasting anything. See `GET /v1/address/:address/inventory/prepared`. ## Detach A Counterparty message that moves every attached balance a transaction spends to one explicit address, as an ordinary address balance. ## Detach delivery The default checkout delivery mode. Output 0 of a settlement is an encrypted detach `OP_RETURN` naming the delivery address, so the buyer receives an address balance, not a new asset UTXO. One detach delivers every unit in a cart. The delivery address can be any Counterparty address family. ## Attached delivery The purchased balance stays on a new asset UTXO at the trading address. Checkout supports this for a single acquired unit. The retained BTC is shown separately from fees in the quote. ## Signing, delivery, and source addresses The three address roles. Signing is P2TR or P2WPKH; delivery is any family; source (the balance an attach spends) is P2PKH, P2WPKH, or P2TR. See [Authentication](/docs/authentication/#three-address-roles). ## Listing A seller's `SIGHASH_SINGLE|ANYONECANPAY` signature over one asset UTXO and a payment output worth the price, held (encrypted) by the market until a buyer completes it. ## Preflight A server-side draft that composes and verifies a transaction for you to sign. Listing preflights are durable and idempotent; quotes and accept preflights reserve nothing. ## Quote The result of `POST /v1/fills/request`: an unsigned checkout PSBT that lives about three minutes and reserves nothing. The first valid signed completion wins. ## Taker Whoever completes a trade: the buyer of a listing, or the seller who accepts an offer. The taker pays the 2.5% (minimum 1,000 sats) marketplace fee. ## Funding slot A clean BTC output worth exactly an offer's price, named `funding_slot_id = ":"`. One slot pays for one purchase, however many policies or targets it backs. ## Exact-target offer (`exact_offer_v1`) An offer the buyer has pre-signed for specific asset outpoints with `SIGHASH_ALL`. The named seller signs their input and the market broadcasts, without the buyer. ## Exact authorization One such buyer-signed settlement for one asset outpoint. A snapshot: a new or moved outpoint needs a new authorization. ## Counter A seller's private listing addressed to one bidder, at least 1,000 sats above the offer. The bidder fills it through checkout with `counter_id`. ## Collection offer (`funded_policy_offer_v1`) An offer on any unit matching a policy (asset, collection, series, artist, year, supply). The buyer signs a zero-fee version-3 funding parent; any holder of a matching attached unit can accept, co-signed by the market signer. ## Policy The canonical eligibility object a collection offer commits to: `scope`, `asset`, `collection`, `max_supply_units`, `min_supply_units`, `issued_year`, `series`, `artist`, absent keys `null`. ## Funding set A collection offer's funding inputs plus one anchor. Its 1..100 alternatives all spend it, so at most one can fill. ## Anchor A 330-sat market-owned output that every collection-offer funding parent also spends, signed by the market only at acceptance. Spending it lets the market invalidate that funding set's parents on-chain; the parent recreates it on fill. ## Market signer The service that co-signs a collection-offer acceptance only while the offer is live, not soft-cancelled, and the unit matches. ## Soft cancel / hard cancel Soft cancel asks the market signer to refuse an offer (free, instant, trusts the market). Hard cancel spends the funding on-chain (costs a fee, trustless). ## CPFP child A child transaction that pays extra fee for its parent. Exact-offer acceptance may include one paid from the seller's proceeds; a collection offer's acceptance child always pays the whole package. ## Journal The market writes every finalized transaction's exact bytes before it broadcasts, and rebroadcasts those same bytes on recovery. A `202` means journaled but still relaying. ## Session A 24-hour bearer token (`x-session-token`) proving control of one address, obtained by a verified message signature. It never authorizes spending. --- ## HTTP endpoints (90, from openapi.json 1.0.0) ### offers - POST /v1/offer-cancellations/review [session]: Authenticated read-only snapshot for wallet cancellation notification. - GET /v1/collections/{slug}/offer-targets: Returns a bounded cheapest-first snapshot of currently listed asset outpoints matching the same normalized collection policy predicates enforced during exact authorization. - POST /v1/offers [session]: Production requires a session proving control of the shared bid-output address; funding alone is not accepted as identity. - POST /v1/offer-funding-preflights [session]: Checks that one exact-value P2WPKH/P2TR funding outpoint is unspent, asset-free, and owned by the authenticated bidder before any offer policy is written. - POST /v1/offer-acceptance-selections/validate: Non-reserving guard for a seller bulk action. - GET /v1/address/{address}/offers [session]: Your offers - GET /v1/address/{address}/offer-coverage [session]: For the bidder's newest 50 live offers, returns each offer whose policy matches current public listings that its funding slot has not authorized yet, as { offer_id, uncovered, more }. - POST /v1/offers/{id}/cancel [session]: Cancel an offer - POST /v1/offer-placements/{id}/cancel [session]: Cancels every still-active offer policy created by the placement. - POST /v1/offer-slot-releases [session]: Release funds for exact offers: an unsigned self-send of the session owner's own bid slots (live or ended) back to the owner's own script, the cancel Bitcoin enforces. - POST /v1/offers/{id}/exact-authorizations [session]: Buyer composes an exact-target transaction over a known asset outpoint. - POST /v1/offers/{id}/exact-authorizations/batch [session]: Atomically preflights and stores 1..100 known asset-outpoint alternatives over one shared funding slot. - GET /v1/offers/{id}/authorization-targets [session]: Returns the offer owner a cheapest-first list of current public listings that the live offer policy matches but its funding slot cannot yet settle unilaterally: no active or settling authorization over the slot, excluding the bidder's own listings. - POST /v1/exact-offer-authorizations/{id}/bidder-psbt [session]: Verifies the buyer's DEFAULT/ALL signature on Counterparty input 0 and activates the immutable exact authorization. - GET /v1/exact-offer-authorizations/{id} [session]: Private to the named seller (session required). - POST /v1/exact-offer-authorizations/{id}/counter-preflight [session]: Builds a seller-reviewable private listing authorization for the exact asset UTXO covered by a live bidder authorization. - POST /v1/exact-offer-authorizations/{id}/counter [session]: Verifies and stores the seller's private listing signature. - GET /v1/exact-offer-authorizations/{id}/status [session]: Private bidder/seller lifecycle and settlement status for response-loss recovery. - POST /v1/exact-offer-authorizations/{id}/accept [session]: Requires a current session for the operation owner before looking up the private resource; missing and foreign IDs return the same 404. - POST /v1/exact-offer-authorizations/{id}/cancel [session]: Revokes marketplace access. - GET /v1/asset-utxos/{txid}/{vout}/exact-offers: Lists live buyer-authorized offers the current holder can accept unilaterally. - GET /v1/address/{address}/counters [session]: Counteroffers to you - GET /v1/address/{address}/exact-offers [session]: Private paginated seller inbox of live buyer-authorized offers, returned in one indexed read instead of one request per asset outpoint. - GET /v1/address/{address}/exact-acceptances [session]: Private seller view of the offer acceptances the server still owns: claimed but not yet broadcast (claiming or prepared), broadcast and awaiting confirmation, and real losses (conflicted or dropped) from the last day. - POST /v1/offer-funding-templates [session]: Composes the bidder's self-send that mints `slot_count` independent bid outputs from their own asset-free P2WPKH/P2TR inputs, plus change. - POST /v1/offer-funding-authorization-templates [session]: Exact-offer authorization templates (1..7 targets) over one set-aside output (`slot_vout`) of an UNSIGNED offer funding template from `POST /v1/offer-funding-templates`, so one wallet review can sign the funding and these authorizations together (the `fund-and-authorize-offers` wallet bundle). - POST /v1/funded-offers [session]: Places offers on bid outputs the signed funding transaction itself proves. ### market - GET /v1/artists: Browse effective artist credits, including curated corrections. - GET /v1/artists/{key}: Artwork and collection counts plus public fillable listing count and floor. - GET /v1/artists/{key}/assets: Distinct credited assets sorted by asset name. - GET /v1/fee-rate: Current fee rates - GET /v1/attach-fee-estimate: Counterparty's current XCP fee for one attach, as a raw XCP quantity. - GET /v1/collections: List collections - GET /v1/search: Search assets and collections - GET /v1/collections/{slug}: Get a collection - GET /v1/collections/{slug}/assets: Paged collection assets. - GET /v1/collections/{slug}/facets: Collection-wide counts behind the browse rail: series, supply bucket, and editorial trait values. - GET /v1/collections/{slug}/book: Bounded collection trading workspace with ask depth, listing rows, exact-policy offer depth, individual offers, and recent fills. - GET /v1/collections/{slug}/holders: Top collection holders across detached address balances and UTXO-attached balances controlled by the same address. - GET /v1/assets/{asset}: Get an asset - GET /v1/assets/{asset}/book: Asset order book - GET /v1/offer-summaries: Best live individual (asset-scope) offer and best live collection or trait policy each listed asset satisfies, with best_offer the higher of the two (the individual offer on a tie). - GET /v1/listings: Newest listings - GET /v1/address/{address}/listings: Public address storefront. - GET /v1/address/{address}/events [session]: Private owner feed of what happened to this address's orders, newest first: listings filled/delisted/expired/invalidated (as seller), offers accepted/cancelled/expired/retired and exact authorizations used/retired (as bidder), collection offers filled/cancelled/expired/retired (as bidder), and settlements the market saw confirm (as buyer by delivery address, or as seller). - GET /v1/events: Public feed of the whole market, newest first: sales (the /fills tape, with its exclusions), listings placed and delisted/expired/invalidated, and asset and collection offers placed and ended. - GET /v1/fills: Recent sales - GET /v1/analytics: Public snapshot, cached for 60 seconds. ### listings - GET /v1/address/{address}/managed-listings [session]: Private owner view containing public listings and bidder-scoped counters for management. - GET /v1/address/{address}/ended-listings [session]: Private owner view of listings that ended in the last 30 days, newest end first, each with a reason derived from stored market facts. - POST /v1/listings/{id}/reprice [session]: Requires a current session for the operation owner before looking up the private resource; missing and foreign IDs return the same 404. - POST /v1/listings/{id}/reprice-preflight [session]: Returns a fresh wallet-reviewable listing authorization for the same prepared asset UTXO at a new price. - POST /v1/listings/{id}/delist [session]: Delist a listing - POST /v1/listings/batch [session]: The batch form of the per-listing owner actions (whose routes and contracts are unchanged): 1..20 distinct listings, every one sold by the session address, ownership checked for every item before anything is composed or written. - POST /v1/listing-preflights [session]: Reuses an eligible attached UTXO or returns a server-verified Counterparty attach PSBT. - GET /v1/address/{address}/listing-preflights [session]: Owner operation history for recovering a lost preflight-create response. - GET /v1/listing-preflights/{id} [session]: Get a listing draft - POST /v1/listing-preflights/{id}/attach [session]: Rate limited as a signed completion: 120 per minute per session address (or IP when anonymous), 600 per IP across sessions, retries of identical signed bytes still count and must honor 429 and Retry-After; a limiter outage does not block it. - POST /v1/listing-preflights/{id}/discard [session]: Discards a listing draft in either mode when no signed attach is in flight: an unsigned attach, a prepared or priced draft, or a failed or expired operation. - POST /v1/listing-preflights/{id}/price [session]: Binds or replaces the price on prepared inventory and returns the exact unsigned listing authorization. - POST /v1/listing-preflights/{id}/attach-and-list [session]: Journals one signed attach and its signed dependent listing. - POST /v1/listing-preflights/{id}/listing [session]: Rate limited as a signed completion: 120 per minute per session address (or IP when anonymous), 600 per IP across sessions, retries of identical signed bytes still count and must honor 429 and Retry-After; a limiter outage does not block it. ### attachments - GET /v1/transactions/{txid}/attach: On-demand Bitcoin confirmation and Counterparty attach parse status. - POST /v1/transactions/status: Read-only Bitcoin existence checks for at most 25 unique txids, in request order. - POST /v1/transactions/inspect: Read-only recovery of exact signed bytes, limited to 16 inputs. - POST /v1/transactions: Relays a transaction the browser finalized itself (a preparation leg) through the marketplace broadcaster. - POST /v1/attach-templates [session]: Composes and independently verifies one one-unit attach for the client leg runner. - POST /v1/attach-templates/speed-up [session]: Returns an unsigned CPFP child for the session owner's own unconfirmed attach. ### checkout - POST /v1/fills/request [session]: Returns a 180-second, non-reserving PSBT snapshot. - GET /v1/fills/{id} [session]: Private durable operation status for response-loss recovery; never exposes raw settlement bytes or seller PSBTs. - POST /v1/fills/complete [session]: Requires a current session for the operation owner before looking up the private resource; missing and foreign IDs return the same 404. - GET /v1/address/{address}/in-flight-purchases [session]: Session owner only. ### inventory - GET /v1/address/{address}/inventory/attachable: Curated address-level Counterparty balances that can be attached for listing. - GET /v1/address/{address}/inventory/prepared: Curated exact one-unit attached outpoints available for listing. ### auth - POST /v1/auth/session: Rate limited per IP: 60 per minute, whether or not a session is presented. - POST /v1/auth/challenge: Rate limited per IP: 60 per minute, whether or not a session is presented. - POST /v1/auth/verify: Rate limited per IP: 60 per minute, whether or not a session is presented. ### policy-offers - POST /v1/policy-offers/preflight [session]: Selects (or verifies) the bidder's confirmed, asset-free funding inputs, assigns a free confirmed market anchor, and builds 1..100 zero-fee v3 alternative parents sharing that funding set, returning the fund_policy_offer wallet request. - POST /v1/policy-offers [session]: Verifies every bidder-signed parent against chain prevouts and the committed leaf, seals the parents at rest, and stores the funding set, all alternatives, and the anchor commitment atomically (all or none). - POST /v1/policy-offers/{id}/cancel [session]: Soft cancel: forwards the bidder's BIP-322 cancel to the market signer, which refuses to sign this parent from then on, and retires the alternative. - POST /v1/policy-offers/{id}/cancel-transaction [session]: Hard cancel: an unsigned self-send of every funding input back to the bidder, which kills every alternative of the funding set on chain. - GET /v1/address/{address}/policy-offers [session]: The bidder's policy offers, newest first (opaque keyset cursor). - GET /v1/assets/{asset}/policy-offers: Live policy offers this asset matches (asset scope plus collection/trait policies of its curated collections), best price first, each with an estimated seller net at the current fee rate. - GET /v1/collections/{slug}/policy-offers: A collection's live policy demand: collection/trait offers plus asset offers on its members, with estimated seller nets. - POST /v1/policy-accepts/preflight [session]: For any attached, confirmed, one-unit asset UTXO the session owns (listed or merely prepared): checks eligibility and builds the v3 acceptance child (the seller pays the marketplace fee and the package network fee), returning the accept_policy_offer wallet request. - POST /v1/policy-accepts [session]: The seller-signed child: verified byte-for-byte against the quote, then atomically claims the funding set and the asset outpoint (first valid signature wins across checkout, exact offers, and accepts), is co-signed by the market signer, journaled with its parent, and submitted as a package. - GET /v1/policy-accepts/{id} [session]: Lifecycle of one acceptance (never its bytes). - GET /v1/address/{address}/policy-accepts [session]: The seller's signed-or-later acceptances, newest first (opaque keyset cursor). ### Artists - POST /v1/artists/{key}/edit [session]: Check current artist editing eligibility - POST /v1/artists/{key}/profile [session]: Save artist profile with fresh ownership and version checks Full reference: https://api.digirare.com/docs/reference/