Create a 0Pools quote
POST /pools/{id}/quote - Lock a short-lived quote for an entitled pool, including the on-ramp delivery address contract.
0Pools API pages are for approved headless partners. They cover the partner-visible quote, transact, status, trade, and balance lifecycle only.
Pricing a conversion and locking a quote is the first money-shaped step in the lifecycle. A quote captures the side, the pair, the amount, the partner-visible economics, and — for a buy — the delivery address. Your server reads the quote, shows the user only the partner-visible economics, and either executes once before expiry or lets the quote lapse.
Endpoint
| Field | Value |
|---|---|
| Method | POST |
| Path | /v1/pools/{id}/quote |
| Area | Quotes |
| Operation id | createQuote |
| Auth boundary | Secret key from your server. |
The {id} path parameter is a pool id this partner is entitled to, discovered from GET /pools. A pool id you are not entitled to returns 404 (cross-tenant access is never 403).
Use it for
Price a specific conversion and lock a short-lived, single-use quote for an entitled pool. For a buy (on_ramp), the call also captures and locks the on-chain delivery address that the purchased crypto will be sent to at settlement.
Use this endpoint only for the partner-scoped resource it describes. Store your own reference id, the returned quoteId, the request id, timestamps, and the current status so support and reconciliation do not depend on browser callbacks alone.
Production rules
- Keep secret keys on your server. This endpoint requires a
sk_*key. - Validate environment, mode, entitlement, asset, network, and amount before the call.
- For a buy, validate the delivery address before requesting the quote — it is locked at this step and cannot be changed later.
- Branch on machine-readable status, error code, object id, and request id.
- Treat examples and placeholder ids as fake data only.
Request body
| Field | Required | Type | Use it for |
|---|---|---|---|
side | Yes | on_ramp/off_ramp | Direction of the conversion. See Sides and the amount role. |
fiatCurrency | Yes | string (ISO 4217) | Three-letter fiat code, for example EUR. |
cryptoCurrency | Yes | string | Crypto asset symbol, for example USDT. |
amount | Yes | decimal string | Positive amount. Its unit depends on side — see below. Always a string, never a float. |
cryptoNetwork | No (default tron) | tron/ethereum/bsc/polygon/solana | The chain the trade transacts on; selects the pool and deposit address. Locked on the quote and never read from the transact body. Distinct from destNetwork. |
type | No (default firm) | firm/indicative | Whether to lock an executable quote or only return a price. See Firm vs indicative. |
destAddress | Required for an on_ramp buy (crypto delivery) | string (EVM 0x…) | Wallet address the purchased crypto is delivered to. Validated and locked on the quote. See On-ramp delivery. |
destNetwork | No | string | EVM delivery network for destAddress. Defaults to the quoted cryptoNetwork when omitted. See On-ramp delivery. |
Money and rate values are strings
Every monetary and rate value in 0Pools is a decimal string, never a float. Basis-point fields are integers.
Sides and the amount role
side sets the direction of the conversion and therefore what amount means.
side | Meaning | amount is… | The response describes… |
|---|---|---|---|
on_ramp | Buy crypto with fiat (fiat → crypto) | the fiat you pay in | the crypto the user receives. |
off_ramp | Sell crypto for fiat (crypto → fiat) | the crypto you sell | the fiat the user receives. |
rate is always expressed as output per unit of input for the chosen side, as a decimal string.
off_ramp is gated
Selling is not publicly enabled by default. A POST /pools/{id}/transact for an off_ramp quote deny-closes with 501 until your account is enabled for it, and even then requires a crypto-receipt step first. You can still request off_ramp quotes, but plan the flow around the gate. See Execute against a quote.
Firm vs indicative
type controls whether the call locks an executable quote or just returns a price.
type | Persisted? | Returns quoteId? | executable | Use it for |
|---|---|---|---|---|
firm (default) | Yes | Yes | true | A real, single-use lock you can pass to transact before it expires. |
indicative | No | No | false | A price-only preview for display or planning. It cannot be transacted. |
A firm quote returns a quoteId and an expiresAt roughly 15 seconds in the future. It is single-use: once you transact it (or reject it, or it expires) it cannot be reused. Show the user only the partner-visible economics and execute once before expiresAt.
An indicative quote returns available: true with executable: false and no quoteId and no expiresAt. It is never persisted and can never be passed to transact. Use it to render a price without committing inventory.
Only act on executable: true. Never attempt to transact an indicative quote, and never transact a quote that is reported unavailable.
On-ramp delivery contract
For a buy (on_ramp), destAddress is required and is captured and locked onto the quote at this step.
- It is validated as an EVM
0x…(40-hex) address against an EVM-only allowlist. The EIP-55 checksum is enforced only for mixed-case addresses; an all-lowercase or all-uppercase address passes on format. destNetwork, when provided, must be one of the allowed EVM networks; when omitted it defaults to the quotedcryptoNetwork.- At
transact, the locked delivery target is copied onto the trade from the quote, never from the transact body — there is no way to redirect delivery after the quote is locked. - The crypto is delivered to that address when the trade settles.
- A missing
destAddresson anon_rampquote is a400.
Delivery networks are EVM-only:
| Network | Common token standard |
|---|---|
| Arbitrum | (e.g. USDT on Arbitrum) |
| Ethereum | ERC-20 |
| BSC | BEP-20 |
| Optimism | (EVM) |
| Polygon | (EVM) |
The customer only needs an EVM wallet address
Because delivery is EVM-only, the end customer needs nothing more than a single EVM wallet address — no wallet on any other chain. Read GET /pools/{id}/capabilities for the exact supportedNetworks instead of hard-coding them.
Response
The response is always HTTP 200 — both an available quote and an unavailable pool return 200. Branch on the available flag, not on the status code.
| Field | When present | Use it for |
|---|---|---|
available | Always | true for a locked or priced quote; false when the pool cannot be quoted. |
type | When available: true | Echoes firm or indicative. |
executable | When available: true | true only for a firm quote that can be passed to transact. |
quoteId | Firm quotes only | Join quote, trade, status, and reconciliation records; pass to transact. |
rate | When available: true | Partner-visible all-in rate (output per input), as a decimal string. |
spreadBps | When available: true | Pool FX spread baked into the rate, in basis points (capped at 50). |
feeBps | When available: true | Your tier draw fee baked into the rate, in basis points (not capped). |
minOrderUsdt | When available: true | Minimum order size for this pool, in USDT (number). |
maxOrderUsdt | When available: true | Maximum order size for this pool, in USDT (number), or null when there is no cap. |
expiresAt | Firm quotes only | Quote expiry (~15s out). Prevent stale accept buttons. |
unavailableReason | When available: false | Safe machine-readable reason. No quoteId is issued. |
For per-pool spread and fee ceilings and order limits, read GET /pools/{id}/capabilities rather than hard-coding values.
Unavailable (fail-soft) response
When the route cannot be priced, the API returns 200 with available: false, an unavailableReason, and no quoteId. This is a normal outcome, not an error — never treat it as a failure to retry in a tight loop, and never attempt to transact it.
unavailableReason | Meaning |
|---|---|
pool_dry | The pool has no available depth for this conversion right now. |
engine_unavailable | Pricing is temporarily unavailable. |
rate_unavailable | No current rate could be sourced for the pair. |
Show the user a partner-visible unavailable state, optionally offer another pool, and back off before re-quoting.
Examples
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",
"cryptoNetwork": "tron",
"type": "firm",
"destAddress": "0x52908400098527886E0F7030069857D2E4169EE7",
"destNetwork": "arbitrum"
}'{
"available": true,
"type": "firm",
"executable": true,
"quoteId": "pq_test_8f3c000000000123",
"rate": "1.0731",
"spreadBps": 25,
"feeBps": 30,
"minOrderUsdt": 10,
"maxOrderUsdt": 50000,
"expiresAt": "2026-01-01T00:00:15Z"
}A firm quote is single-use. Store the quoteId and expiresAt, then call POST /pools/{id}/transact once before it expires.
{
"available": true,
"type": "indicative",
"executable": false,
"rate": "1.0731",
"spreadBps": 25,
"feeBps": 30,
"minOrderUsdt": 10,
"maxOrderUsdt": 50000
}Price-only. There is no quoteId and no expiresAt; it cannot be transacted. Re-request a firm quote when the user is ready to commit.
Errors
All errors use the unified envelope and carry an X-Request-Id response header. Branch on code/type/statusCode, not on the free-form message.
{
"type": "invalid_request",
"code": "invalid_request",
"message": "Example validation error using fake data.",
"request_id": "req_test_000000000123",
"doc_url": null,
"statusCode": 400
}| Status | type | When it happens |
|---|---|---|
400 | invalid_request | Bad body — missing destAddress on an on_ramp, a non-checksummed/invalid EVM address, a destNetwork not on the allowlist, a bad amount, or an unknown currency/asset. |
401 | unauthorized | Missing or invalid secret key. |
403 | forbidden | Access denied. Branch on code: pools_not_enabled, pool_access_suspended, kyc_not_approved, pool_not_allowed, key_mode_mismatch. |
404 | not_found | Pool not found or not entitled to this partner. Cross-tenant access is 404, never 403. |
429 | rate_limited | Request throttled. Back off and retry. |
5xx | server_error | Transient server or upstream failure. Retry with bounded backoff. |
Unavailable is not an error
A dry or temporarily unquotable pool returns 200 with available: false — not a 4xx/5xx. Reserve error handling for the statuses above.
Public boundary
This reference covers partner-visible discovery, quote, transact, status, trade, and balance behavior. Liquidity operations, routing internals, settlement venues and networks, provider details, reserve logic, and runbooks are outside the public API contract.