0Pools operations
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.
0Pools is the headless liquidity surface for approved partners. This page is the operations narrative that ties the endpoints together; each endpoint has its own reference page with the full request and response shape. These docs stay at the partner-safe lifecycle level and never expose provider, treasury, reserve, bank, routing, or risk-control internals.
0Pools is gated early access
0Pools is provisioned per partner by 0Bit. It is not GA and not self-serve. Access, KYC status, tier, and entitled pools are operator-provisioned. Use 0Gate for hosted buy/sell/swap UX; use 0Pools only for approved headless partners that own their user experience.
Authentication
| Key | Where it runs | What it can do |
|---|---|---|
pk_* | Discovery only | List entitled pools. Publishable; safe to use from your discovery layer. |
sk_* | Server-side only | Everything else: quote, transact, status, trades, balance, capabilities, funding, ledger. Never ship to a browser. |
Every key is partner-scoped. A request for a resource that belongs to another partner resolves as 404, never 403 — cross-tenant access is indistinguishable from "not found". Money and rate values are decimal strings; bps values are integers.
Endpoint map
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET | /pools | pk_* | List entitled pools and availability. |
GET | /pools/{id}/capabilities | sk_* | Self-describing networks, sides, fees, and limits. |
GET | /pools/{id}/balance | sk_* | Partner-visible pool balance and entitlement view. |
GET | /pools/funding | sk_* | Runtime deposit instructions for the pre-funded fiat balance. |
POST | /pools/{id}/quote | sk_* | Lock a firm quote (or price an indicative one). |
POST | /pools/{id}/transact | sk_* | Execute once against a quote. Idempotency-Key required. |
GET | /pools/quotes/{quoteId} | sk_* | Retrieve a quote and its derived status. |
POST | /pools/quotes/{quoteId}/reject | sk_* | Decline a quote so it cannot be executed. |
GET | /pools/transactions/{quoteId} | sk_* | Poll a trade by quote id. This poll advances settlement. |
GET | /pools/trades | sk_* | List partner trades with cursor pagination and filters. |
GET | /pools/trades/{transactId} | sk_* | Pure read of one trade. Does not advance settlement. |
GET | /pools/ledger | sk_* | Partner-scoped balance movements for reconciliation. |
Lifecycle
The production rule: a quote is short-lived and single-use. Your server requests the quote, stores the quote id and expiry, shows the user only the partner-visible economics, and transacts once if the user accepts before expiry. The browser never holds a secret key or creates a quote.
Discover and describe
Start with discovery and read the pool's own description instead of hard-coding networks, fees, or limits.
GET /pools(pk_*) returns{ pools: [...] }, where each pool hasid,pair,fiatCurrency,cryptoCurrency, andavailable(a boolean). Treat availability as current state, not a promise.GET /pools/{id}/capabilities(sk_*) is self-describing:supportedNetworks,sides,tier,drawFeeBps,spreadCapBps,minOrderUsdt,maxOrderUsdt, and your dedicatedcryptoDepositAddress. Read this rather than assuming order limits or supported networks.
See List entitled 0Pools and Read pool capabilities.
Quote
POST /pools/{id}/quote prices a conversion. Choose type:
firm(default) locks a short-TTL (~15s), single-use quote and returns aquoteId,rate,spreadBps,feeBps,expiresAt, andexecutable: true. Only a firm quote can be transacted.indicativereturns price-only economics withexecutable: falseand noquoteId. It is not persisted and cannot be transacted.
When pricing is unavailable, the call fails soft: HTTP 200 with { "available": false, "unavailableReason": "pool_dry" | "engine_unavailable" | "rate_unavailable" } and no quoteId. Never attempt to transact without a quoteId.
The amount is a decimal string whose meaning depends on side:
side | amount is... | Response fiat / output |
|---|---|---|
on_ramp | fiat paid in | crypto delivered. rate is output-per-input. |
off_ramp | crypto sold | fiat you receive. rate is output-per-input. |
For an on_ramp (buy), destAddress is required (optional destNetwork) and is captured and locked at quote time — a missing destAddress on an on_ramp returns 400. See Create a 0Pools quote and the Quotes and pricing guide.
curl -X POST https://pools-api.0bit.app/v1/pools/pool_test_123/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",
"destAddress": "0x1111111111111111111111111111111111111111",
"destNetwork": "arbitrum"
}'Transact
POST /pools/{id}/transact executes a firm quote. The body carries only the quoteId; side, amounts, rate, and the on-ramp delivery address all come from the locked quote, never the body.
Idempotency-Key is required
Transact is a money write, so the Idempotency-Key header is mandatory. It is also idempotent on the quoteId itself — re-posting the same quote returns the same trade. Reusing an Idempotency-Key with a different body is rejected as 409 (idempotency-conflict). Retry timeouts with the same key and the same body.
curl -X POST https://pools-api.0bit.app/v1/pools/pool_test_123/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"
}'A successful call reserves the trade and returns { transactId, quoteId, status: "reserved", ... }. A reserved trade is not final — poll to progress it. See Execute against a 0Pools quote.
If the user declines, call POST /pools/quotes/{quoteId}/reject. Rejecting is money-free and sets the quote to rejected, a state distinct from consumed. Reject is not idempotent: a second reject of an already consumed, rejected, or expired quote returns 409.
Poll to a terminal state
GET /pools/transactions/{quoteId} is the intended, idempotent way to advance a reserved trade. Each poll moves it toward a terminal state.
Read vs. advance
GET /pools/transactions/{quoteId} advances settlement. GET /pools/trades/{transactId} is a pure read and does not advance anything. Use the quote-id poll to progress; use the trade-id read for support lookups.
| Status | Meaning |
|---|---|
quoted | Quote locked; not yet executed. |
reserved | Accepted and reserved by transact. Poll to progress. |
settled | Terminal success. Delivery completed. |
failed | Terminal failure. |
released | Terminal release; the reservation was unwound. |
returned | A previously settled trade later reversed downstream, with an automatic refund to your balance. |
returned is poll-only
A settled trade can later flip to returned (a fiat payout returned by the receiving bank, or a crypto delivery that failed downstream). The refund to your pool balance is automatic and idempotent. There is no webhook for returned — detect it by polling the status. Terminal webhooks cover only pool.transaction.settled, .failed, and .released.
See Get 0Pools transaction status and 0Pools webhooks.
Funding, balance, and ledger
0Pools trades draw from a pre-funded partner balance. These reads let you fund, check headroom, and reconcile.
GET /pools/fundingreturns your dedicated runtime deposit instructions for the pre-funded fiat balance —{ currency, iban, reference, instructions }. Funding is EUR-only: the optional?currencydefaults toEUR, and any non-EUR currency returns400. It returns404when the balance gate is off, andiban: nullwith an ops note when not yet provisioned. See Get pool funding.GET /pools/{id}/balancereturns{ poolId, pair, available, tier, allowedPools, balance }, whereavailableis a boolean,allowedPoolsis a string array, andbalanceis the partner pre-funded fiat balance as a decimal string (ornullwhen the balance gate is off);poolId,pair, andtierare plain strings. Access and KYC states are not echoed here — they surface only as403denial codes. See Read 0Pools balance.GET /pools/ledgerlists partner-scoped credits and debits for reconciliation. Use it alongsidetransactId/quoteIdto tie balance movements to trades. See Read pool ledger.
List trades with GET /pools/trades for support and reconciliation: cursor pagination (?cursor=<last transactId>, response { data, nextCursor, hasMore }, limit 1–100 default 50) with side, status, created_after, and created_before filters.
Delivery and sides
| Topic | Behavior |
|---|---|
| On-ramp delivery | EVM-only — Arbitrum, Ethereum (ERC20), BSC (BEP20), Optimism, Polygon (e.g. "USDT on Arbitrum"). The customer needs only an EVM wallet address; destAddress is validated as a 0x 40-hex address at quote time (EIP-55 checksum is enforced only for mixed-case addresses; all-lowercase or all-uppercase pass on format). |
| Off-ramp (sell) | Gated. off_ramp transact deny-closes with 501 unless enabled for your partner, pool, and environment; when enabled it requires a dedicated crypto-receipt address, credited crypto balance, and approved payout destination before fiat payout. |
Errors and limits
Errors use a unified envelope { type, code, message, request_id, doc_url, statusCode }, and every response carries an X-Request-Id header. Branch on the machine-readable code.
| Status | Meaning |
|---|---|
400 | Validation (e.g. missing destAddress on on_ramp). |
401 | Auth — missing or invalid key. |
402 | Insufficient pre-funded balance. |
403 | Access denial; branch on code. The pool-access codes pools_not_enabled, pool_access_suspended, kyc_not_approved, and pool_not_allowed apply only to entitled routes (quote, transact, capabilities, balance); secret-only read routes (funding, ledger, trades, quotes, transactions, reject) return only key_mode_mismatch. |
404 | Not found or cross-tenant id. |
409 | Quote expired/consumed, reservation-failed, or idempotency-conflict. |
429 | Per-partner rolling 24h notional velocity cap exceeded (default off; configured per partner; idempotent retries are excluded). |
501 | off_ramp not enabled for this partner, pool, or environment. |
Order size limits come from minOrderUsdt/maxOrderUsdt on /pools/{id}/capabilities. See Errors.
Public boundary
This reference covers partner-visible discovery, quote, transact, status, trade, balance, funding, and ledger behavior. Liquidity operations, routing internals, provider and bank details, reserve logic, rebalancing thresholds, and operator runbooks are outside the public API contract and must stay out of browser code and customer-facing UI.
Related pages
Partner-safe Pools API
Use the partner-safe 0Pools lifecycle for entitled pool discovery, short-lived quotes, one-time execution, trade status, funding, and reconciliation.
Errors and retries
Handle validation errors, auth failures, state conflicts, rate limits, retryable failures, request ids, and idempotent writes.