Quotes
Reserving a lot, the two clocks running against you, and how a payment turns itself into a sale.
A quote is a reservation with a price on it. Creating one pulls wallets out of the catalog, freezes what they cost, and mints a payment address that exists for this order and nothing else. Nobody else can buy those wallets while your quote holds them.
Creating one
You either name the wallets or describe them.
// name them: the lot is exactly this
{ "chain": "solana", "wallet_ids": ["…", "…", "…"] }
// describe them: the server picks at reserve time
{ "chain": "solana", "count": 25, "min_age_days": 90, "min_balance": "50000000" }With wallet_ids, count is ignored and the id set defines the lot. If any one
of them has already gone, the call fails with 409 and reserves nothing.
Partial lots are never created.
Two options worth knowing about:
min_gap_secondsspaces the lot out, so consecutive wallets'funded_attimestamps will be at least this far apart. It only applies to server-side selection and is ignored when you passwallet_ids. With an explicit lot, spacing is your job, usingmin_funding_gap_secondson the catalog.buyer_refis a free-text label of your own. It rides along to the sale and onto the wallets, and every history endpoint can filter on it. If you resell to end customers, this is how you keep track of who got what.
You can also bundle a top-up into the order so the wallets arrive already funded. See Top up wallets.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
curl -X POST "https://example.com/quote" \ -H "Content-Type: application/json" \ -d '{ "chain": "string" }'{ "buyer_ref": "string", "chain": "string", "completed_at": "string", "created_at": "string", "expected_amount": "string", "hard_expiry": "string", "id": "string", "items": [ { "balance": "string", "our_price": "string", "token_holdings": [ { "balance": "string", "decimals": 0, "mint": "string", "symbol": "string", "ui_multiplier": "string", "usd_value": 0 } ], "user_price": "string", "wallet_id": "string" } ], "margin_total": "string", "org_id": "string", "our_price_total": "string", "pay_address": "string", "pay_checkout_url": "string", "pay_intent_id": "string", "payment_status": "string", "payment_tx_hash": "string", "received_amount": "string", "reserved_until": "string", "status": "string", "topup_max": "string", "topup_min": "string", "topup_per_wallet": "string", "topup_plan_id": "string", "topup_required": "string", "topup_total": "string", "total_due": "string", "user_price_total": "string"}Two clocks, not one
The response carries two deadlines and they do different jobs.
reserved_until is the working hold, 15 minutes by default. When it lapses,
the wallets go back to the catalog.
hard_expiry is the ceiling, 30 minutes from creation. It does not move,
ever.
POST /quote/{id}/refresh pushes reserved_until out by another full hold
window, as often as you like, right up until hard_expiry. Past it, refresh
returns 409 and the order is over. The point of the pair is that a buyer who
is still typing their transfer keeps the lot, while a tab left open overnight
does not.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Path Parameters
Response Body
application/json
curl -X POST "https://example.com/quote/string/refresh"{ "buyer_ref": "string", "chain": "string", "completed_at": "string", "created_at": "string", "expected_amount": "string", "hard_expiry": "string", "id": "string", "items": [ { "balance": "string", "our_price": "string", "token_holdings": [ { "balance": "string", "decimals": 0, "mint": "string", "symbol": "string", "ui_multiplier": "string", "usd_value": 0 } ], "user_price": "string", "wallet_id": "string" } ], "margin_total": "string", "org_id": "string", "our_price_total": "string", "pay_address": "string", "pay_checkout_url": "string", "pay_intent_id": "string", "payment_status": "string", "payment_tx_hash": "string", "received_amount": "string", "reserved_until": "string", "status": "string", "topup_max": "string", "topup_min": "string", "topup_per_wallet": "string", "topup_plan_id": "string", "topup_required": "string", "topup_total": "string", "total_due": "string", "user_price_total": "string"}Paying
Send total_due to pay_address, on the quote's chain. That is the
whole payment flow. total_due is user_price_total plus topup_required, so
if you send user_price_total on an order that carries a top-up, you have
underpaid and nothing will settle.
The address is fresh, single-use, and belongs to this quote alone. There is no memo, tag or reference to attach. The address is the reference.
While you wait, poll GET /quote/{id} and watch three fields.
| Field | What it tells you |
|---|---|
received_amount | how much has landed so far |
expected_amount | how much has to land |
payment_status | pending, then received, then swept |
An underpayment shows up as received_amount sitting below expected_amount.
Send the difference to the same address and it settles once the total covers the
bill.
Settlement is automatic. The API watches the payment address on-chain and
closes the order itself the moment the balance covers expected_amount. You
do not have to tell it the money arrived.
POST /quote/{id}/settle exists for the cases where you would rather not wait
for the watcher. It re-reads the balance immediately and settles if it is
covered. It also accepts a buyer_ref, which is the usual reason to call it:
stamping the reference at the last moment, once you know which of your customers
the order was for. Calling it on an already-settled quote is harmless.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Path Parameters
Response Body
application/json
curl -X GET "https://example.com/quote/string"{ "buyer_ref": "string", "chain": "string", "completed_at": "string", "created_at": "string", "expected_amount": "string", "hard_expiry": "string", "id": "string", "items": [ { "balance": "string", "our_price": "string", "token_holdings": [ { "balance": "string", "decimals": 0, "mint": "string", "symbol": "string", "ui_multiplier": "string", "usd_value": 0 } ], "user_price": "string", "wallet_id": "string" } ], "margin_total": "string", "org_id": "string", "our_price_total": "string", "pay_address": "string", "pay_checkout_url": "string", "pay_intent_id": "string", "payment_status": "string", "payment_tx_hash": "string", "received_amount": "string", "reserved_until": "string", "status": "string", "topup_max": "string", "topup_min": "string", "topup_per_wallet": "string", "topup_plan_id": "string", "topup_required": "string", "topup_total": "string", "total_due": "string", "user_price_total": "string"}Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Path Parameters
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
curl -X POST "https://example.com/quote/string/settle" \ -H "Content-Type: application/json" \ -d '{}'{ "buyer_ref": "string", "chain": "string", "completed_at": "string", "created_at": "string", "expected_amount": "string", "hard_expiry": "string", "id": "string", "items": [ { "balance": "string", "our_price": "string", "token_holdings": [ { "balance": "string", "decimals": 0, "mint": "string", "symbol": "string", "ui_multiplier": "string", "usd_value": 0 } ], "user_price": "string", "wallet_id": "string" } ], "margin_total": "string", "org_id": "string", "our_price_total": "string", "pay_address": "string", "pay_checkout_url": "string", "pay_intent_id": "string", "payment_status": "string", "payment_tx_hash": "string", "received_amount": "string", "reserved_until": "string", "status": "string", "topup_max": "string", "topup_min": "string", "topup_per_wallet": "string", "topup_plan_id": "string", "topup_required": "string", "topup_total": "string", "total_due": "string", "user_price_total": "string"}Statuses
| Status | Meaning |
|---|---|
reserved | holding wallets, waiting for money |
paid / settling | payment seen, sale being written |
settled | done. Keys available, sale recorded |
expired | both clocks ran out, wallets released |
cancelled | you called cancel, wallets released |
failed | the order could not be completed |
The last four are terminal. Nothing moves a quote out of them.
Collecting the keys
Once the quote is settled, GET /quote/{id}/keys returns one entry per
wallet, with private_key_hex carrying the key. The call is idempotent, so ask
as often as you need. The answer does not change and nothing is consumed.
Asking before settlement returns 400 telling you the current status. See
Wallets & keys for what is actually in that
field, because on Solana it is not hex despite the name.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Path Parameters
Response Body
application/json
curl -X GET "https://example.com/quote/string/keys"[ { "chain": "string", "private_key_hex": "string", "pubkey": "string", "purged_at": "string", "wallet_id": "string" }]Backing out
POST /quote/{id}/cancel releases the wallets immediately. It only works on a
reserved quote. Once payment has been seen there is nothing to cancel, and you
get a 409. Letting a quote expire has the same effect, just slower. Cancel is
the polite version, and it puts the inventory back in the catalog for everyone
else straight away.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Path Parameters
Response Body
application/json
curl -X POST "https://example.com/quote/string/cancel"{ "ok": true}Listing
GET /quotes returns your organisation's quotes, newest first, filterable by
status and buyer_ref. It is offset-paged and each entry carries its full
item list, which makes it a heavier call than it looks. Page it rather than
fetching everything.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Query Parameters
Filter to a single buyer reference. Combine with status=reserved to recover a buyer's still-unpaid quote (e.g. after a page refresh).
nullint6450int640nullResponse Body
application/json
curl -X GET "https://example.com/quotes"[ { "buyer_ref": "string", "chain": "string", "completed_at": "string", "created_at": "string", "expected_amount": "string", "hard_expiry": "string", "id": "string", "items": [ { "balance": "string", "our_price": "string", "token_holdings": [ { "balance": "string", "decimals": 0, "mint": "string", "symbol": "string", "ui_multiplier": "string", "usd_value": 0 } ], "user_price": "string", "wallet_id": "string" } ], "margin_total": "string", "org_id": "string", "our_price_total": "string", "pay_address": "string", "pay_checkout_url": "string", "pay_intent_id": "string", "payment_status": "string", "payment_tx_hash": "string", "received_amount": "string", "reserved_until": "string", "status": "string", "topup_max": "string", "topup_min": "string", "topup_per_wallet": "string", "topup_plan_id": "string", "topup_required": "string", "topup_total": "string", "total_due": "string", "user_price_total": "string" }]Last updated on