Hushxima
Marketplace

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_seconds spaces the lot out, so consecutive wallets' funded_at timestamps will be at least this far apart. It only applies to server-side selection and is ignored when you pass wallet_ids. With an explicit lot, spacing is your job, using min_funding_gap_seconds on the catalog.
  • buyer_ref is 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.

POST
/quote

Authorization

bearerAuth
AuthorizationBearer <token>

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.

POST
/quote/{id}/refresh

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Path Parameters

id*String

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.

FieldWhat it tells you
received_amounthow much has landed so far
expected_amounthow much has to land
payment_statuspending, 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.

GET
/quote/{id}

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Path Parameters

id*String

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"}
POST
/quote/{id}/settle

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Path Parameters

id*String

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

StatusMeaning
reservedholding wallets, waiting for money
paid / settlingpayment seen, sale being written
settleddone. Keys available, sale recorded
expiredboth clocks ran out, wallets released
cancelledyou called cancel, wallets released
failedthe 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.

GET
/quote/{id}/keys

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Path Parameters

id*String

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.

POST
/quote/{id}/cancel

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Path Parameters

id*String

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.

GET
/quotes

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Query Parameters

buyer_ref?|

Filter to a single buyer reference. Combine with status=reserved to recover a buyer's still-unpaid quote (e.g. after a page refresh).

Defaultnull
limit?integer
Formatint64
Default50
offset?integer
Formatint64
Default0
status?|
Defaultnull

Response 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

On this page