Hushxima
Marketplace

Catalog

Browsing the inventory that is for sale, and where each wallet's price comes from.

The catalog is the set of wallets currently listed for sale. Wallets leave it the moment a quote reserves them and never come back, so treat a page of results as a snapshot rather than a stable list.

What a wallet looks like

Each entry carries the history that makes it worth buying:

  • balance, what the wallet holds right now, in base units.
  • funded_at, first_tx_at, last_tx_at, its timeline. Age is derived from funded_at.
  • tx_count, how many transactions it has made.
  • funding_source and funding_exchange, where the money originally came from. The first names the provider, the second the actual exchange when the wallet was funded from one.
  • tier, persona_name, timezone_name, the quality band and the behavioural profile the wallet was aged under.
  • token_holdings, non-native tokens sitting in the wallet, if any.

What it does not carry is the address. pubkey is present on every entry and always empty: the address of a wallet nobody has bought never leaves the platform, and it reaches you with the keys once the order settles.

The listing also leaves out everything that only means something once a wallet has an owner. status, owner_org_id, buyer_ref, sold_at and sale_id are on GET /wallets, where they describe your own inventory, and not here.

Two prices, and they are not the same

cost_basis is what the wallet costs the platform. user_price is what you will be charged, and it is the one that matters: platform margin for the wallet's tier, plus your own organisation's resale markup, on top of the base.

On a catalog response user_price is computed live, with exactly the pricing a quote would apply, so the number you display is the number the order charges. On a sold wallet the same field means something slightly different: the price actually paid, frozen at settlement.

Both are in base units. Convert to USD with GET /prices if you are showing them to a human. For where each of those numbers comes from (the funding cost underneath, the tier margin, and your own), see Price breakdown.

Filtering

GET /catalog takes the filters you would expect, covering chain, balance range, price range, age range and transaction count, plus a few that are specific to this inventory:

  • sources, tier, persona and timezone are comma-separated multi-selects: tier=tier1,tier2.
  • min_funding_gap_seconds thins the results so that no two returned wallets were funded within that many seconds of each other. A batch of wallets all funded in the same minute is a pattern, and this is how you avoid buying one.
  • gap_with takes a comma-separated list of Unix timestamps and keeps only wallets whose funded_at is at least min_funding_gap_seconds away from every one of them. Useful when you already hold wallets and want the next lot not to line up with them. It requires min_funding_gap_seconds to be set.

How a page is dealt

A page is spread before it is returned, whichever sort you asked for. It alternates between funding sources, tiers, personas, timezone profiles and funding windows, and your sort applies within each of those groups.

So a page reads as your sort dealt across the inventory rather than as a plain ordering. Ask for two funding sources sorted by price and you get both, cheapest first within each, instead of the cheaper one's entire stock. sort still defaults to random, where the spread is the whole order and order is ignored.

A spread page is addressed by position rather than by the value of its sort column. Follow next_cursor and you will not notice.

diversify=false turns all of it off and gives you a plain sorted page, for a caller that needs the sort column strictly monotonic from one page to the next. It is accepted on /catalog, /catalog/count and /catalog/facets alike.

Which inventory you are looking at

By default the catalog is the marketplace: wallets the platform produced and sells. An organisation with the private marketplace switched on also has a stock of its own, the wallets its own funding plans produced, and scope=private reads that one instead.

The two never mix. A private wallet is listed for its organisation only, is never priced (user_price comes back as "0", it is your stock), and a quote on it is a delivery rather than a sale. scope is accepted on /catalog, /catalog/count, /catalog/facets and /catalog/sources alike, and scope=private answers 403 for an organisation without the feature.

GET
/catalog

Authorization

bearerAuth
AuthorizationBearer <token>

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

In: header

Query Parameters

chain?string|null
count?integer

Max rows for this page (per-request cap; page past it via cursor).

Formatint64
Default50
cursor?|

Opaque keyset cursor from a prior response's next_cursor. When set, returns the page strictly after it.

Defaultnull
diversify?|

true (default) | false. While on, the page is spread across the facets a buyer filters on (funding source, cex/classic kind, tier, persona, timezone profile, funding window), so every value the filters allow is represented instead of the sort column handing the whole page to one of them (sources=kucoin,gate&sort=price coming back all-kucoin). The requested sort still picks which wallet of a group comes first, so the page reads as that sort dealt round-robin across facets. Set false for a strictly monotonic sort column (exports, price ladders); ignored for sort=random, which is the diversified order.

gap_with?|

Comma-separated reference timestamps (Unix epoch seconds). A wallet is kept only if its funded_at is at least min_funding_gap_seconds away from each of them, so its funding gap never collides with these times. Composes with the mutual spacing above.

max_age_days?|
Formatint64
max_balance?string|null
max_price?string|null
max_tx_count?|
Formatint32
min_age_days?|
Formatint64
min_balance?|

Balance bounds, in base units (lamports on Solana, wei on EVM) — the same scale as WalletDto.balance. Convert native → base client-side.

min_funding_gap_seconds?|

Minimum funding gap, in seconds. When set (>0) it enforces mutual spacing: the returned wallets are thinned along funded_at so no two are funded within this many seconds of each other (like a quote's min_gap_seconds). The same threshold also applies to gap_with when that is provided. Required when gap_with is set.

Formatint64
min_price?|

Price bounds on our price (cost_basis), also in base units (same scale as balance). For USD filtering convert client-side via /prices.

min_tx_count?|

Transaction-count bounds (inclusive) on tx_count.

Formatint32
order?|

asc (default) | desc.

persona?|

Comma-separated persona names (multi-select), e.g. memecoin_degen,hodler.

scope?|

public (default): the marketplace catalogue. private: the org's own inventory, available to orgs with the private marketplace enabled.

sort?|

random (default) | age | balance | price | tx_count. random has no sort column at all: the page is the facet rotation over a reproducible shuffle, and order is ignored in that mode.

source?string|null
sources?|

Comma-separated funding sources (multi-select), e.g. binance,kraken.

tier?|

Comma-separated quality tiers (multi-select), e.g. tier1,tier2.

timezone?|

Comma-separated timezone-profile names (multi-select).

Response Body

application/json

curl -X GET "https://example.com/catalog"
{  "items": [    {      "balance": "string",      "chain": "string",      "cost_basis": "string",      "created_at": "string",      "first_tx_at": "string",      "funded_at": "string",      "funding_exchange": "string",      "funding_source": "string",      "id": "string",      "last_tx_at": "string",      "network": "string",      "persona_name": "string",      "pubkey": "string",      "source_kind": "string",      "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"}

Counting and faceting before you fetch

GET /catalog/count answers the same filters with a single number. It is cheap enough to call on every keystroke of a filter form.

GET /catalog/facets returns the bounds and histograms of the current selection: maximum balance, price, transaction count and age, four histograms to draw sliders against, and the distinct tiers, personas and timezones available. It is what you build a filter panel out of.

GET /catalog/sources lists the distinct funding sources present, optionally narrowed to one chain.

GET
/catalog/count

Authorization

bearerAuth
AuthorizationBearer <token>

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

In: header

Query Parameters

chain?string|null
count?integer

Max rows for this page (per-request cap; page past it via cursor).

Formatint64
Default50
cursor?|

Opaque keyset cursor from a prior response's next_cursor. When set, returns the page strictly after it.

Defaultnull
diversify?|

true (default) | false. While on, the page is spread across the facets a buyer filters on (funding source, cex/classic kind, tier, persona, timezone profile, funding window), so every value the filters allow is represented instead of the sort column handing the whole page to one of them (sources=kucoin,gate&sort=price coming back all-kucoin). The requested sort still picks which wallet of a group comes first, so the page reads as that sort dealt round-robin across facets. Set false for a strictly monotonic sort column (exports, price ladders); ignored for sort=random, which is the diversified order.

gap_with?|

Comma-separated reference timestamps (Unix epoch seconds). A wallet is kept only if its funded_at is at least min_funding_gap_seconds away from each of them, so its funding gap never collides with these times. Composes with the mutual spacing above.

max_age_days?|
Formatint64
max_balance?string|null
max_price?string|null
max_tx_count?|
Formatint32
min_age_days?|
Formatint64
min_balance?|

Balance bounds, in base units (lamports on Solana, wei on EVM) — the same scale as WalletDto.balance. Convert native → base client-side.

min_funding_gap_seconds?|

Minimum funding gap, in seconds. When set (>0) it enforces mutual spacing: the returned wallets are thinned along funded_at so no two are funded within this many seconds of each other (like a quote's min_gap_seconds). The same threshold also applies to gap_with when that is provided. Required when gap_with is set.

Formatint64
min_price?|

Price bounds on our price (cost_basis), also in base units (same scale as balance). For USD filtering convert client-side via /prices.

min_tx_count?|

Transaction-count bounds (inclusive) on tx_count.

Formatint32
order?|

asc (default) | desc.

persona?|

Comma-separated persona names (multi-select), e.g. memecoin_degen,hodler.

scope?|

public (default): the marketplace catalogue. private: the org's own inventory, available to orgs with the private marketplace enabled.

sort?|

random (default) | age | balance | price | tx_count. random has no sort column at all: the page is the facet rotation over a reproducible shuffle, and order is ignored in that mode.

source?string|null
sources?|

Comma-separated funding sources (multi-select), e.g. binance,kraken.

tier?|

Comma-separated quality tiers (multi-select), e.g. tier1,tier2.

timezone?|

Comma-separated timezone-profile names (multi-select).

Response Body

application/json

curl -X GET "https://example.com/catalog/count"
{  "count": 0}
GET
/catalog/facets

Authorization

bearerAuth
AuthorizationBearer <token>

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

In: header

Query Parameters

chain?string|null
count?integer

Max rows for this page (per-request cap; page past it via cursor).

Formatint64
Default50
cursor?|

Opaque keyset cursor from a prior response's next_cursor. When set, returns the page strictly after it.

Defaultnull
diversify?|

true (default) | false. While on, the page is spread across the facets a buyer filters on (funding source, cex/classic kind, tier, persona, timezone profile, funding window), so every value the filters allow is represented instead of the sort column handing the whole page to one of them (sources=kucoin,gate&sort=price coming back all-kucoin). The requested sort still picks which wallet of a group comes first, so the page reads as that sort dealt round-robin across facets. Set false for a strictly monotonic sort column (exports, price ladders); ignored for sort=random, which is the diversified order.

gap_with?|

Comma-separated reference timestamps (Unix epoch seconds). A wallet is kept only if its funded_at is at least min_funding_gap_seconds away from each of them, so its funding gap never collides with these times. Composes with the mutual spacing above.

max_age_days?|
Formatint64
max_balance?string|null
max_price?string|null
max_tx_count?|
Formatint32
min_age_days?|
Formatint64
min_balance?|

Balance bounds, in base units (lamports on Solana, wei on EVM) — the same scale as WalletDto.balance. Convert native → base client-side.

min_funding_gap_seconds?|

Minimum funding gap, in seconds. When set (>0) it enforces mutual spacing: the returned wallets are thinned along funded_at so no two are funded within this many seconds of each other (like a quote's min_gap_seconds). The same threshold also applies to gap_with when that is provided. Required when gap_with is set.

Formatint64
min_price?|

Price bounds on our price (cost_basis), also in base units (same scale as balance). For USD filtering convert client-side via /prices.

min_tx_count?|

Transaction-count bounds (inclusive) on tx_count.

Formatint32
order?|

asc (default) | desc.

persona?|

Comma-separated persona names (multi-select), e.g. memecoin_degen,hodler.

scope?|

public (default): the marketplace catalogue. private: the org's own inventory, available to orgs with the private marketplace enabled.

sort?|

random (default) | age | balance | price | tx_count. random has no sort column at all: the page is the facet rotation over a reproducible shuffle, and order is ignored in that mode.

source?string|null
sources?|

Comma-separated funding sources (multi-select), e.g. binance,kraken.

tier?|

Comma-separated quality tiers (multi-select), e.g. tier1,tier2.

timezone?|

Comma-separated timezone-profile names (multi-select).

Response Body

application/json

curl -X GET "https://example.com/catalog/facets"
{  "age_hist": [    0  ],  "balance_hist": [    0  ],  "count": 0,  "max_age_days": 0.1,  "max_balance": "string",  "max_price": "string",  "max_tx": 0,  "personas": [    "string"  ],  "price_hist": [    0  ],  "tiers": [    "string"  ],  "timezones": [    "string"  ],  "tx_hist": [    0  ]}
GET
/catalog/sources

Authorization

bearerAuth
AuthorizationBearer <token>

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

In: header

Query Parameters

chain?string|null
scope?|

public (default) or private (the org's own inventory).

Response Body

application/json

curl -X GET "https://example.com/catalog/sources"
{  "sources": [    "string"  ]}

From a page to an order

There are two ways to turn a selection into a quote.

Let the server pick. Pass the same filters to POST /quote with a count, and it selects that many matching wallets at reserve time. Nothing you saw needs to still be there.

Pick them yourself. Send the wallet_ids you showed the buyer, and the lot is exactly what they chose. If any single one has been reserved by someone else in the meantime, though, the whole call fails with 409 and reserves nothing.

The second is the right choice when a human has been looking at a list. The first is the right choice for anything automated.

Last updated on

On this page