Wallets & keys
Your inventory after the sale. Retrieving keys, the format they come in, and tracking who each wallet is for.
Once an order settles, the wallets are yours. They keep their whole history
(balance, age, transaction count, tier, persona) and gain three fields: the sale
they came from, the price they went out at, and a buyer_ref if you set one.
Your inventory
GET /wallets lists the wallets your organisation owns. It is cursor-paged and
filterable by chain and buyer_ref. Each row carries key_purged_at, null
while the platform still holds the wallet's key and set once it no longer does,
so a list can show which wallets can still be exported without asking for each
key.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Query Parameters
Opaque keyset cursor from a prior response's next_cursor. When set, returns the page strictly after it.
nullint6450Response Body
application/json
curl -X GET "https://example.com/wallets"{ "items": [ { "aging_status": "string", "balance": "string", "buyer_ref": "string", "chain": "string", "cost_basis": "string", "created_at": "string", "first_tx_at": "string", "funded_at": "string", "funding_exchange": "string", "funding_source": "string", "id": "string", "imported": true, "inventory_org_id": "string", "key_purged_at": "string", "last_tx_at": "string", "network": "string", "owner_org_id": "string", "persona_name": "string", "pubkey": "string", "sale_id": "string", "sold_at": "string", "source_kind": "string", "status": "string", "target_age_days": 0, "tier": "string", "timezone_name": "string", "token_holdings": [ { "balance": "string", "decimals": 0, "mint": "string", "post_sale": true, "sellable_balance": "string", "sellable_usd_value": 0, "symbol": "string", "ui_multiplier": "string", "usd_value": 0 } ], "tx_count": 0, "user_price": "string" } ], "next_cursor": "string"}Price breakdown
A wallet is not priced from a list. It is priced from what it cost to produce, and two margins are applied on top of that: ours, then yours.
The anchor: what went into the wallet
cost_basis is the native currency the platform funded the wallet with to
create it and age it. It is the floor of everything below: a wallet is never
sold for less than the money sitting in it.
The wallet's live balance can raise that anchor but never lower it, so the
base is whichever of the two is higher.
base = max(cost_basis, balance)Read that as a floor rather than as a formula. Almost every listed wallet has spent a good part of its native balance on the activity that makes it worth buying, and the anchor is what keeps it priced at what went in rather than at what is left. The other side is the exception: a wallet whose balance actually grew past its funding is priced on what it now holds, so a wallet that made money is not handed over at cost.
Our margin, which varies with the wallet
On top of that base the platform takes a margin that depends on the wallet's
tier, the quality band it was aged under. tier0 is funded and nothing
more; the higher bands carry real activity, held positions, and cross-chain
history, all of which take longer to build and are worth more to hold. A
tier3 wallet therefore carries a different rate from a tier0 one, on the same
base.
our_price = base × (1 + platform margin for that tier)The rate is configured per tier and per organisation, so this is your price rather than a public one. Nothing in the API lets you change it; it is what the platform has agreed with you.
Your margin, which is yours
On top of our price, your organisation applies whatever markup it wants, and it varies with the tier the same way ours does.
user_price = our_price × (1 + your markup for that tier)
margin = user_price − our_priceYour four rates live on your organisation config as markup_tiers and you set
them with PUT /config, one at a time or all at once, unless they have been
fixed for you. See Organisation. The platform does
not cap them. What you resell at is your business.
| Field | What it is |
|---|---|
cost_basis | what the platform funded the wallet with |
our_price | the platform's price for you: base plus the tier margin |
user_price | what the order charges: our_price plus your markup |
margin | the difference, which is yours |
A quote totals these into our_price_total, user_price_total and
margin_total. total_due, the number the payment address waits for, is built
from user_price_total. All of them are base units, on the quote's chain.
Your margin does not need collecting. When the order settles, margin_total is
credited to your organisation's balance on that chain, as one ledger entry per
order, and you withdraw it when you like. See Balance.
A top-up bought with the order is not part of this. It
sits in topup_required, is passed through at cost plus the funding fee, and
earns no margin either way. That is why total_due and user_price_total are
two different numbers.
Catalog prices are computed with exactly this arithmetic at the moment you ask,
so the figure you display is the figure the order charges. On a wallet you
already own, user_price means the price actually paid, frozen at settlement.
Getting a key
GET /wallets/{id}/key returns one wallet's private key. It works for any
wallet you own, whichever order it came from, which makes it the endpoint to use
long after the purchase. GET /quote/{id}/keys is the other one: it hands back
a whole lot at once, and it is what you call right after settlement.
Both are idempotent and neither consumes anything. For what happens to a key while we are still holding it, see Security.
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/wallets/string/key"{ "chain": "string", "private_key_hex": "string", "pubkey": "string", "purged_at": "string", "wallet_id": "string"}Key formats
The field is called private_key_hex, and on Solana it is not hex. The encoding
follows what each ecosystem's wallets actually import.
| Chain | Encoding |
|---|---|
solana | base58 of the full 64-byte ed25519 keypair (seed ‖ pubkey), the format Phantom, Solflare and solana-keygen produce and accept |
ethereum, base, bsc, robinhood | hex of the 32-byte secp256k1 key |
Solana wallets reject hex, and they reject the bare 32-byte seed too, so there
was not much of a choice. Read the chain field on the response and decode
accordingly.
When the key is gone
If your organisation has opted into key retention, we stop holding the keys of
the wallets you buy a set number of minutes after the sale. Past that window the
response still comes back, but with private_key_hex: null and purged_at set
to the moment it was dropped.
This is off by default, and turning it on is a one-way door for each wallet. Past the window we cannot hand the key back, because we do not have it. Make sure your own custody is in place before asking for it.
Deleting keys yourself
You do not have to wait for a policy. DELETE /wallets/{id}/key purges one
wallet's key on the spot, and POST /wallets/keys/purge does it for up to five
hundred wallet ids in one call. Both only touch wallets your organisation owns,
and both leave everything else about the wallet alone: it stays in your
inventory, keeps its history and its buyer reference, it just can no longer be
exported from here.
The bulk call reports one outcome per id rather than failing as a whole:
outcome | Meaning |
|---|---|
purged | the key is gone |
already_purged | it was gone before this call |
farming | the platform still farms this wallet for you and needs the key; stop the farm first |
not_owned | not your wallet, or no such wallet, and nothing was touched |
The single-wallet route answers the same situations with status codes: 409 for
a wallet still being farmed, 404 for one that is not yours.
Same one-way door as retention. Export the key first, then delete it.
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 DELETE "https://example.com/wallets/string/key"{ "outcome": "string", "wallet_id": "string"}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.
Wallets whose private keys the org wants purged from the platform.
Response Body
application/json
curl -X POST "https://example.com/wallets/keys/purge" \ -H "Content-Type: application/json" \ -d '{ "wallet_ids": [ "string" ] }'{ "purged": 0, "results": [ { "outcome": "string", "wallet_id": "string" } ]}Selling a wallet out of its tokens
An aged wallet arrives holding whatever positions its history gave it. They are
part of what makes it look lived-in, and once it is yours they are your
positions. POST /wallets/{id}/sell-all unwinds them: one swap per token, back
to the chain's native asset.
Nothing is transferred out. The proceeds land on the wallet that held the tokens, which is the only place they can go without handing you a second address to reconcile. What comes back is one operation id per token found, queued rather than done.
The same wallet is addressable by its public key, which is the identifier you
are likelier to have in hand once the keys have been exported:
POST /wallets/by-pubkey/{pubkey}/sell-all. An address that does not exist and
an address that belongs to another organisation both answer 404.
To sell the whole lot at once, ask for it at purchase time instead, with
sell_tokens_after_purchase on the quote. Same
mechanism, applied to every wallet in the order the moment it settles.
A wallet whose key we no longer hold, under
key retention, is refused with
409. We cannot sign for it any more. Sell it out before the window closes,
or do it yourself with the key you exported.
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/wallets/string/sell-all"{ "chain": "string", "operation_ids": [ "string" ], "tokens": 0, "wallet_id": "string"}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/wallets/by-pubkey/string/sell-all"{ "chain": "string", "operation_ids": [ "string" ], "tokens": 0, "wallet_id": "string"}What is still in there
GET /wallets/{id}/tokens, or /wallets/by-pubkey/{pubkey}/tokens, reads the
chain at the moment you ask and lists what the wallet still holds, each entry
with its mint, symbol, raw balance, decimals and a live usd_value when the
mint is priced. has_tokens is the short answer.
It also tells you what is already on its way out, which is the difference between "nothing left to sell" and "the sale has not landed yet":
| Field | What it means |
|---|---|
has_queued_sell | a sell-out queued earlier has not settled |
has_queued_withdraw | a withdrawal is in flight for this wallet |
queued_operations | the operations behind those two, with their kind, status and mint |
A wallet can list tokens and carry has_queued_sell: true at the same time. The
swap is queued, not done. Poll rather than queueing a second sell-out.
Not everything a wallet holds is ours to sell. A sell-out unwinds what farming
left on the wallet, not positions its owner opened after taking delivery. Each
holding therefore carries sellable_balance and sellable_usd_value, the share
a sell-out would actually sell, and post_sale: true marks a token that
appeared after the wallet changed hands. has_sellable_tokens on the response
is the short answer to "is there anything left for a sell-out to do".
On Solana the listing is exhaustive, since a wallet's token accounts can be read straight from the chain. EVM chains offer no equivalent, so the balances checked there are those of the tokens we know the wallet to have held. An unusual token can fall outside that set, in which case it is neither listed here nor sold by a sell-out.
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/wallets/string/tokens"{ "chain": "string", "count": 0, "has_queued_sell": true, "has_queued_withdraw": true, "has_sellable_tokens": true, "has_tokens": true, "pubkey": "string", "queued_operations": [ { "created_at": "string", "id": "string", "kind": "string", "mint": "string", "status": "string", "to_address": "string", "tx_hash": "string" } ], "sellable_usd_value": 0, "tokens": [ { "balance": "string", "decimals": 0, "mint": "string", "post_sale": true, "sellable_balance": "string", "sellable_usd_value": 0, "symbol": "string", "ui_multiplier": "string", "usd_value": 0 } ], "total_usd_value": 0, "wallet_id": "string"}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/wallets/by-pubkey/string/tokens"{ "chain": "string", "count": 0, "has_queued_sell": true, "has_queued_withdraw": true, "has_sellable_tokens": true, "has_tokens": true, "pubkey": "string", "queued_operations": [ { "created_at": "string", "id": "string", "kind": "string", "mint": "string", "status": "string", "to_address": "string", "tx_hash": "string" } ], "sellable_usd_value": 0, "tokens": [ { "balance": "string", "decimals": 0, "mint": "string", "post_sale": true, "sellable_balance": "string", "sellable_usd_value": 0, "symbol": "string", "ui_multiplier": "string", "usd_value": 0 } ], "total_usd_value": 0, "wallet_id": "string"}Buyer references
buyer_ref is a free-text label you attach to wallets and orders. Nothing in
the API interprets it. It exists so you can answer "which of my customers has
this wallet". You can set it at quote creation, at settlement, or afterwards.
Four endpoints manage it after the fact:
PUT /wallets/{id}/ownersets or clears it on one wallet.POST /wallets/reassignmoves every wallet from one reference to another and returns how many moved.POST /wallets/unassignclears one reference across the board.GET /wallets/userslists the distinct references currently in use, which is the raw material for a customer picker.
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 PUT "https://example.com/wallets/string/owner" \ -H "Content-Type: application/json" \ -d '{}'{ "ok": true}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/wallets/reassign" \ -H "Content-Type: application/json" \ -d '{ "from_buyer_ref": "string", "to_buyer_ref": "string" }'{ "count": 0}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/wallets/unassign" \ -H "Content-Type: application/json" \ -d '{ "buyer_ref": "string" }'{ "count": 0}Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Response Body
application/json
curl -X GET "https://example.com/wallets/users""string"Putting money in them
Wallets bought here arrive with whatever balance they had. To add native currency, to them or to any address you own elsewhere, see Top up wallets.
Last updated on