Create an order
POST /v1/orders — requires the orders:write scope.
Request body
Exact types, bounds and formats for every field are in the
API Reference; the bounds that bite are collected on
Request limits.
About
items:
- Every product must belong to the key’s store, be
listed, and share a single currency with the rest of the cart. - A
productIdthat appears more than once has its quantities summed into one line — a cart is deduplicated, not rejected. - The order total is Σ (product price × quantity).
A longer
expiresIn holds any reserved delivery stock out of circulation for
exactly as long. Widen the window deliberately, not by default.The Idempotency-Key header
Optional, 1 to 255 characters, unique per store. Retrying a create with a key this store
has already used returns the original order — same id, same everything — instead
of minting a duplicate, no matter how the request body changed. Keys never expire, so
use one per logical purchase; your own purchase id is the natural choice. Full semantics
in Idempotency.
Example
201 Created
unitPrice is a fiat decimal string with no trailing-zero padding — "50" is $50.00.
The create response deliberately returns only what you need to proceed;
GET /v1/orders/{id} returns the full order.
Errors
All of these are400 with a { "error": … } body.
Anything the request schema rejects never reaches the order logic — the validator
answers first, always with the generic
{ "error": "Invalid request data" }.
The messages below are the ones the order logic itself produces, once the body’s shape is
already known to be valid.
Quantity bounds come from the product’s delivery configuration and default to a
minimum of 1 and no maximum. See
Quantity bounds.
Product does not belong to this store is the API’s one deliberate exception to
reporting another store’s resources as simply missing. Treat it as a signal you have
crossed keys and stores, not as a lookup to depend on — see
Errors.
Retrieve an order
GET /v1/orders/{id} — requires orders:read. {id} is the order’s public short id.
200 OK
paymentMethod, cryptoAmount, receivedAmount and transactions all describe a
single payment attempt, chosen in this order: the attempt that completed
the order; otherwise the best-funded one, holding the largest receivedAmount;
otherwise the most recent. They are null (and transactions is []) while no
attempt has been started.order.partial webhook, which reports the attempt the indexer credited.
A well-formed id that does not resolve — undecodable, unknown, or an order belonging to
another store — answers 404 with { "error": "Order not found" }. An order you cannot
see is reported as missing, never as forbidden. A malformed id is a 400; see
Errors.
List orders
GET /v1/orders — requires orders:read. Newest first.
200 OK
Expire an order
POST /v1/orders/{id}/expire — requires orders:write.
Ends a pending order now rather than waiting out its window. This fires the
order.expired webhook and returns any reserved delivery stock to the pool, which
is the main reason to call it: an abandoned cart holding the last five licence keys is
worth reclaiming.
Returns 200 with the full order, now expired.
Like voiding an invoice,
the call retires every still-watchable attempt first, so no lapsed-but-unreaped
deposit address is left collecting funds for an order that can no longer be paid. If one
of those attempts was holding funds, retiring it turns the order
partial — terminal —
and the call then fails with Only pending orders can be expired. Re-read the order to
see the partial status.
An order that was already expired when the call arrived is refused with
Only pending orders can be expired. The one case that returns a body instead is the
narrow race where a concurrent request expires it after this one read it — then you get
the expired order back, because your intent already holds.
Statuses
Note that picking a coin does not change the order’s status — it creates a payment
attempt under the order, which stays
pending. See
Payment lifecycle for the attempt-level state
machine and what each transition means.
Multi-item and currency rules
One currency per order
An order snapshots exactly one currency, taken from its products. Mixing a USD product with a EUR product isAll items must use the same currency. If you sell in both, keep
separate carts.
Duplicate products are merged
The response’sitems array may therefore be shorter than the array you sent.
Prices are snapshotted
items[].title, unitPrice and image are captured at creation. Editing the product
later never rewrites a past order — which is what makes webhooks and receipts
trustworthy after the fact.
Stock reservation is all-or-nothing
Every line item that needs finite stock reserves it inside one transaction. If any line cannot, the whole create fails withProduct is out of stock and nothing is held.
Reservations are released when the order expires.