Pools API
Use the partner-safe 0Pools lifecycle for entitled pool discovery, short-lived quotes, one-time execution, trade status, funding, and reconciliation.
The Pools API is the headless liquidity surface for approved partners. Because 0Pools is partner-gated and liquidity-sensitive, the docs stay at the safe lifecycle level: discover entitled pools, read pool capabilities, request a short-lived quote, accept or reject it, transact once, poll or retrieve status, and reconcile partner-visible records.
This page deliberately does not publish treasury balances, provider names, reserve allocation, market-maker terms, routing strategy, bank rails, rebalancing thresholds, or internal risk controls.
0Pools is not the default public ramp
Use 0Gate for hosted buy/sell/swap UX. Use 0Pools only for approved headless partners that own their user experience and have the required compliance, entitlement, and operating model.
Gated early access
0Pools is in gated early access, provisioned per partner by 0Bit. It is not GA and not self-serve. Access, status, KYC state, tier, and allowed pools are operator-provisioned; you cannot grant them through the API.
What this page covers
- The safe Pools API lifecycle, end to end.
- Which calls use publishable keys and which require secret keys.
- Quote (firm vs indicative), transact, reject, status, trade, capabilities, funding, balance, and ledger behavior.
- Idempotency, HTTP status handling, velocity limits, and reconciliation rules.
- What must stay out of browser code, customer-facing UI, and support artifacts.
Value types
Money is strings, bps are integers
Money and rate values are decimal strings (for example "100.00"), never floats. Basis-point fields
(spreadBps, feeBps, totalBps, drawFeeBps, spreadCapBps) are integers. Preserve strings end to end so you
never lose precision through a float round-trip.
Endpoint map
| Method | Path | Area | Purpose | Auth boundary |
|---|---|---|---|---|
GET | /pools | Discovery | List pools this partner is entitled to, with availability. | Publishable or secret key, discovery only. |
GET | /pools/{id}/capabilities | Discovery | Read a pool's self-describing networks, sides, fees, and limits. | Secret key, partner-scoped. |
POST | /pools/{id}/quote | Quotes | Lock a short-lived firm quote, or price an indicative one. | Secret key, server-side. |
POST | /pools/{id}/transact | Trades | Execute once against a firm quote. | Secret key, server-side, idempotent. |
GET | /pools/quotes/{quoteId} | Quotes | Retrieve a quote without consuming or extending it. | Secret key, partner-scoped. |
POST | /pools/quotes/{quoteId}/reject | Quotes | Explicitly decline a quote so it cannot be transacted. | Secret key, server-side. |
GET | /pools/transactions/{quoteId} | Trades | Poll trade status by quote id (advances settlement). | Secret key, partner-scoped. |
GET | /pools/trades | Trades | List partner trades with cursor pagination and filters. | Secret key, partner-scoped. |
GET | /pools/trades/{transactId} | Trades | Retrieve one trade (pure read, does not advance settlement). | Secret key, partner-scoped. |
GET | /pools/{id}/balance | Balance | Read a partner-visible balance and entitlement view. | Secret key, partner-scoped. |
GET | /pools/funding | Funding | Read your dedicated deposit instructions for the fiat balance. | Secret key, partner-scoped. |
GET | /pools/ledger | Reconciliation | List partner-scoped balance movements (credits/debits). | Secret key, partner-scoped. |
Key boundary
Publishable keys (pk_*) are discovery-only — they list entitled pools and nothing else. Every quote, transact,
status, trade, balance, capability, funding, and ledger call requires a secret key (sk_*) used server-side only.
All responses are partner-scoped; a request for another partner's object returns 404, never 403.
Lifecycle
The important production rule is that a firm quote is short-lived and single-use. Do not let the browser create or execute it directly. Your server should request the quote, store the quote id and expiry, show the user only the partner-visible economics, and execute once if the user accepts before expiry.
Trade status lifecycle
A trade moves through PoolTxnStatus values. Polling the status by quote id is the intended, idempotent way to advance a reserved transaction toward a terminal state.
| Status | Meaning |
|---|---|
quoted | A firm quote exists but has not been transacted. |
reserved | transact accepted the quote and reserved the trade; poll to progress it. |
settled | The trade completed and was delivered. |
released | The reservation was released without settling. |
failed | The trade could not be completed. |
returned | A previously settled trade was reversed (a payout returned by the receiving bank, or a delivery that failed downstream). |
'returned' is poll-only and auto-refunds
A settled trade can later flip to returned. When that happens, the trade amount is auto-refunded to your partner
balance with an idempotent, conflict-guarded credit (visible in GET /pools/ledger). There is no webhook for
returned — surface it through the status poll, and reconcile the refund from the ledger.
Discovery
GET /pools returns the pools the partner is entitled to, including an availability signal. Treat that signal as current state, not a promise that every future quote will be available.
curl -X GET https://pools-api.0bit.app/v1/pools \
-H "Authorization: Bearer pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Use discovery to populate UI choices, cache lightly, and hide unavailable pools. Do not expose internal route details or fallback providers.
Capabilities
GET /pools/{id}/capabilities is self-describing. Read it instead of hard-coding networks, sides, fees, or order limits — these are configured per partner and per pool and can change.
curl -X GET https://pools-api.0bit.app/v1/pools/EUR-USDT/capabilities \
-H "Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"| Capability field | Use it for |
|---|---|
poolId / pair | Identify the pool and currency pair. |
fiatCurrency / cryptoCurrency | The fiat and crypto legs supported by the pool. |
supportedNetworks | The delivery networks you may pass as destNetwork (EVM-only). |
sides | Which sides (on_ramp / off_ramp) are enabled for this pool. |
tier | The partner's tier for this pool. |
drawFeeBps / spreadCapBps | Approved economic bounds (integers, basis points). |
minOrderUsdt / maxOrderUsdt | Per-order minimum and maximum. |
cryptoDepositAddress | Your dedicated 0Pools deposit address for off-ramp crypto receipts. |
Quote
POST /pools/{id}/quote prices a specific conversion. A firm quote (the default) locks a short time-to-live quote when the route is available; an indicative quote is price-only.
curl -X POST https://pools-api.0bit.app/v1/pools/EUR-USDT/quote \
-H "Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"side": "on_ramp",
"fiatCurrency": "EUR",
"cryptoCurrency": "USDT",
"amount": "100.00",
"type": "firm",
"cryptoNetwork": "tron",
"destAddress": "0xAbC0000000000000000000000000000000000123",
"destNetwork": "arbitrum"
}'type selects the quote kind (firm or indicative, default firm). cryptoNetwork selects the crypto network for the quote (tron, ethereum, bsc, polygon, or solana, default tron); the on-ramp delivery network is set separately by destNetwork from the EVM-only allowlist.
Firm vs indicative quotes
A firm quote (default) is executable: it returns a quoteId, a short expiresAt (about 15 seconds), rate,
spreadBps, feeBps, and executable: true. Only firm quotes can be transacted. An indicative
quote is price-only: it returns no quoteId, executable: false, and is not persisted. Use it to preview pricing,
never to transact.
| Quote field | Use it for |
|---|---|
quoteId | Join quote, trade, status, and reconciliation records (firm only). |
executable | Decide whether the UI can proceed to transact. |
rate | Show partner-visible pricing (output-per-input). |
spreadBps / feeBps | Show approved economics where allowed. |
expiresAt | Prevent stale accept buttons (firm only). |
available / unavailableReason | Explain why a route cannot be used without exposing internals. |
Amount semantics. For on_ramp, amount is the fiat you pay in and the quoted rate (output-per-input) describes the crypto you receive. For off_ramp, amount is the crypto you sell and the quoted rate describes the fiat you receive. rate is always output-per-input; derive the opposite leg from amount × rate.
Unavailable routes fail soft, never lock
When a route cannot be priced, the quote returns HTTP 200 with available: false, an unavailableReason
(pool_dry, engine_unavailable, or rate_unavailable), and no quoteId. There is nothing to transact — show
the reason and let the user choose another route. Do not retry in a tight loop.
On-ramp delivery
For a buy (on_ramp), destAddress is required and is captured and locked at quote time — validated as an EVM 0x address (a mixed-case address must pass an EIP-55 checksum; all-lowercase or all-uppercase addresses pass on format), restricted to the EVM-only network allowlist. The address is copied onto the trade from the quote at transact (never re-read from the transact body) and delivered on settle. A missing destAddress on an on_ramp quote returns 400.
Delivery networks are EVM-only — for example, USDT on Arbitrum. Read the exact set from supportedNetworks in /capabilities rather than hard-coding it. The customer needs only an EVM wallet address; no wallet on any other chain is required.
Accept or reject
If the user accepts, call POST /pools/{id}/transact with the quote id from your server.
curl -X POST https://pools-api.0bit.app/v1/pools/EUR-USDT/transact \
-H "Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 00000000-0000-4000-8000-000000000123" \
-d '{
"quoteId": "quote_test_123"
}'Idempotency-Key is required on transact
transact is a money write and requires an Idempotency-Key header. It is also idempotent on quoteId: a safe
retry returns the existing trade rather than creating a duplicate. Reusing the same key with a different body returns
409. The quote call does not require an idempotency key.
A successful transact returns the reserved trade (transactId, quoteId, status: reserved, …); poll to progress it. Transact can fail with 402 (insufficient balance to reserve), 409 (quote expired, already consumed, or reservation failed), or 501 (off-ramp not enabled for this partner, pool, or environment — see below).
If the user declines, call POST /pools/quotes/{quoteId}/reject. Rejecting is money-free and sets the quote's status to rejected — a distinct terminal state from consumed, so a rejected quote can never be transacted. If a quote is already consumed, rejected, or expired, refresh state and branch instead of retrying blindly.
Derived quote status precedence
A quote's status is derived from active → consumed | expired | rejected, with precedence
rejected > consumed > expired > active. Treat rejected as a deliberate decline, distinct from a
quote that was consumed by a transact.
Off-ramp (sell)
off_ramp is account-gated. It deny-closes with HTTP 501 unless explicitly enabled for the partner, pool, and environment. When enabled, off-ramp requires a crypto-receipt gate: the partner first sends the crypto leg to its dedicated per-partner deposit address from cryptoDepositAddress in /capabilities; 0Pools credits the partner-scoped crypto balance; only then can a sell trade debit that balance and move toward fiat payout. Do not retry a 501 in a loop — request enablement through account review.
Funding
GET /pools/funding returns your dedicated deposit instructions for the pre-funded fiat balance that backs on-ramp orders (EUR-only).
curl -X GET https://pools-api.0bit.app/v1/pools/funding \
-H "Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"| Funding field | Use it for |
|---|---|
currency | The funding currency (EUR). |
iban | The dedicated IBAN provided at runtime (may be null if not yet provisioned). |
reference | The reference to include with your deposit. |
instructions | Partner-visible deposit instructions. |
If the balance gate is off for the partner, this returns 404. If funding is enabled but not yet provisioned, iban is null with an operations note — wait for provisioning rather than retrying in a loop.
Status and reconciliation
Use read endpoints to join the quote to the resulting trade, balance, and ledger.
| Read path | What to store |
|---|---|
GET /pools/quotes/{quoteId} | Quote status, expiry, rate, fee/spread fields, pair, amount, network. |
GET /pools/transactions/{quoteId} | Trade status by quote id. This poll advances settlement. |
GET /pools/trades | Trade history with cursor pagination and filters (reconciliation). |
GET /pools/trades/{transactId} | Trade id, quote id, status, pair, amount, timestamps. Pure read; does not advance settlement. |
GET /pools/{id}/balance | Partner-visible balance and entitlement view. |
GET /pools/ledger | Partner-scoped balance movements (credits/debits) for reconciliation. |
GET /pools/trades uses cursor pagination: pass ?cursor=<last transactId>; the response is { data[], nextCursor, hasMore } with nextCursor null when exhausted. limit is 1–100 (default 50). Filter with side, status, created_after, and created_before.
The balance view returns { poolId, pair, available, tier, allowedPools, balance } with money as decimal strings. It deliberately does not echo access or KYC state (those surface only as 403 denial codes) and exposes no venue or external numbers.
Support and operations should be able to answer: which pool was selected, which quote was shown, whether it expired or was rejected, whether a trade was created, what status it reached, whether it later returned, and which request ids belong to the workflow.
HTTP status handling
Every response carries an X-Request-Id header (mirroring request_id in the unified error envelope { type, code, message, request_id, doc_url, statusCode }). Branch on statusCode, type, and code — never on the free-form message.
| Status | Meaning for Pools | Recommended handling |
|---|---|---|
400 | Validation problem (for example, missing destAddress on on_ramp). | Fix the request or user input; do not retry unchanged. |
401 | Missing or invalid authentication. | Fix credentials before retrying. |
402 | Insufficient balance to reserve at transact. | Fund the balance (see /pools/funding), then retry. |
404 | Object not found or belongs to another partner (cross-tenant). | Check ids and ownership; never surface foreign ids. |
409 | Quote consumed, expired, reservation failed, or idempotency-key/body conflict. | Retrieve current state and branch; refresh the quote if stale. |
429 | Per-partner 24h notional velocity cap exceeded at transact. | Back off; the cap is a rolling window. |
501 | off_ramp transact not enabled for public use. | Do not retry; request enablement through account review. |
403 uses stable denial codes (and 425 is not used)
Access failures return 403 with a stable code (pools_not_enabled, pool_access_suspended, kyc_not_approved,
pool_not_allowed, key_mode_mismatch) — branch on code, not message. See the Errors page for the full denial
table. Pools does not use 425.
Rate limits
Ordinary throttling is applied per client IP. Separately, an optional per-partner rolling 24-hour notional velocity cap can return 429 at transact when exceeded; idempotent retries are excluded so they are not double-counted. The velocity cap is off by default and configured per partner. Per-order minimum and maximum (minOrderUsdt / maxOrderUsdt) come from /capabilities.
Error and retry guidance
| Condition | Recommended handling |
|---|---|
| Quote unavailable | Show the unavailableReason and let the user choose another route; do not retry in a tight loop. |
| Quote expired | Request a new quote and require the user to accept the new terms. |
| Duplicate transact | Reuse the existing trade; the same Idempotency-Key returns the existing trade. |
| Idempotency conflict | A 409 means the key was reused with a different body; reconcile before issuing a new write. |
| Insufficient balance | Fund the fiat balance from /pools/funding, then retry. |
| Off-ramp not enabled | Treat 501 as terminal; request enablement through account review. |
| Foreign or missing quote | Treat as a hard 404; do not expose partner identifiers. |
| Network timeout | Retry transact server-side with the same idempotency key, then read current state. |
Public documentation boundary
Public docs may describe:
- Entitled pool discovery and capabilities.
- Quote request (firm and indicative) and partner-visible quote fields.
- Accept/reject behavior and derived quote status.
- Trade status, the
returnedauto-refund, and partner-scoped reads. - Funding instructions, balance, and ledger for reconciliation.
- Fee and spread fields only where approved.
Public docs must not describe:
- Treasury balances or reserve allocation.
- Liquidity-provider or settlement-provider identities and bank rails.
- Internal routing or rebalancing logic and thresholds.
- Provider contracts or market-maker terms.
- Operational runbooks.
- Risk controls beyond partner-facing status/error guidance.
Related pages
0Pools API overview
Approved partner 0Pools discovery, quote, transact, status, trade, funding, ledger, and balance operation groups.
Operation map
The partner-safe 0Pools operation lifecycle: discover entitled pools, lock a quote, transact once with idempotency, poll settlement to a terminal state, and reconcile against capabilities, funding, and the ledger.