0Pools quickstart
Build an approved 0Pools server-side flow from entitlement discovery to quote, transact, status, webhook, and reconciliation.
This quickstart is the fastest safe path through 0Pools. It shows the developer flow without pretending 0Pools is self-serve. Before this works in production, the partner must be active, KYC/KYB approved, tiered, allowlisted for pools, and provisioned for funding/webhooks where required.
Implementation flow and API details
Start with the flow below to understand the full 0Pools lifecycle. Use the 0Pools API reference for endpoint schemas, request fields, response objects, status codes, and examples.
End-to-end picture
1. Confirm access
No money route works until every gate is open.
| Gate | What must be true | Failure shape |
|---|---|---|
| Global product | 0Pools backend enabled for the environment. | Product disabled. |
| Partner key | Correct sk_* or pk_* for test/live. | 401 or mode mismatch. |
| Partner access | PartnerPoolAccess row exists and is active. | 403. |
| Verification | KYC/KYB status is approved. | 403. |
| Tier | Pricing tier is assigned. | 403 / access suspended. |
| Allowed pools | Requested pool id is in allowedPools. | 403 or cross-tenant 404. |
| Funding | Prefunded balance exists where required. | 402 insufficient balance. |
Before building the flow, confirm which pools, sides, networks, limits, and funding model are approved for your account.
2. Discover entitled pools
Use discovery as the source of truth for what to display.
curl https://pools-api.0bit.app/v1/pools \
-H "Authorization: Bearer $OBIT_PUBLISHABLE_OR_SECRET_KEY"Example response:
{
"pools": [
{
"id": "EUR-USDT",
"pair": "EUR/USDT",
"fiatCurrency": "EUR",
"cryptoCurrency": "USDT",
"available": true
}
]
}Do not hard-code pool ids from examples. Entitlements can differ by partner, market, and environment.
3. Read capabilities
Capabilities tell your UI what it can safely show: sides, networks, fees, limits, and optional deposit instructions.
| Capability | Use it to |
|---|---|
supportedNetworks | Build network selectors. |
sides | Hide disabled buy/sell paths. |
minOrderUsdt / maxOrderUsdt | Validate amount before quote. |
drawFeeBps / spreadCapBps | Explain partner-visible economics. |
cryptoDepositAddress | Show off-ramp deposit details only when enabled. |
4. Preview with an indicative quote
Indicative quotes let you refresh a price while the user edits. They are not persisted and cannot be executed.
curl -X POST https://pools-api.0bit.app/v1/pools/EUR-USDT/quote \
-H "Authorization: Bearer $OBIT_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"side": "on_ramp",
"fiatCurrency": "EUR",
"cryptoCurrency": "USDT",
"amount": "100.00",
"type": "indicative"
}'5. Lock a firm quote
When the user is ready, request a firm quote. A firm quote is short-lived, single-use, and returns quoteId.
curl -X POST https://pools-api.0bit.app/v1/pools/EUR-USDT/quote \
-H "Authorization: Bearer $OBIT_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"side": "on_ramp",
"fiatCurrency": "EUR",
"cryptoCurrency": "USDT",
"amount": "100.00",
"cryptoNetwork": "tron",
"type": "firm",
"destAddress": "0x52908400098527886E0F7030069857D2E4169EE7",
"destNetwork": "arbitrum"
}'For a buy, the destination wallet is locked here. Execution cannot redirect delivery later.
6. Execute once
Execute with an Idempotency-Key. Treat a successful response as reserved, not final settlement.
curl -X POST https://pools-api.0bit.app/v1/pools/EUR-USDT/transact \
-H "Authorization: Bearer $OBIT_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_123_accept_1" \
-d '{ "quoteId": "pq_test_123" }'Example acknowledgement:
{
"transactId": "txn_test_456",
"status": "reserved",
"quoteId": "pq_test_123",
"idempotent": false
}7. Track status
Poll by quote id or consume terminal webhooks.
curl https://pools-api.0bit.app/v1/pools/transactions/pq_test_123 \
-H "Authorization: Bearer $OBIT_SECRET_KEY"| Status | Action |
|---|---|
reserved | Keep showing pending. Do not credit final balance. |
settled | Terminal success. Update your ledger. |
failed | Terminal failure. Follow your refund/support policy. |
released | Clean unwind. Let the user retry with a fresh quote. |
returned | Downstream return after settlement. Reconcile and notify support. |
8. Reconcile
Your production record should be able to answer: who requested the flow, what pool was quoted, what terms were accepted, which trade was created, what terminal state arrived, and which ledger movement resulted.
{
"order_id": "order_123",
"environment": "sandbox",
"pool_id": "EUR-USDT",
"quote_id": "pq_test_123",
"transact_id": "txn_test_456",
"side": "on_ramp",
"amount": "100.00",
"status": "reserved",
"idempotency_key": "order_123_accept_1",
"request_id": "req_..."
}Before and after
| Before 0Pools | With 0Pools |
|---|---|
| Partner builds ad hoc quote logic. | Partner calls one quote lifecycle. |
| User accepts a price with unclear expiry. | Firm quote has explicit expiry. |
| Duplicate clicks can double-submit. | Execution is idempotent. |
| Support searches across unrelated systems. | Quote, trade, request, and event ids join the path. |
| Ledger updates rely on UI state. | Ledger updates from server status/webhook state. |
Production integration artifacts
A production quickstart should leave the partner backend with one durable lifecycle record. Do not split discovery, quote, execution, status, and reconciliation across disconnected tables.
| Artifact | Required content | Why it matters |
|---|---|---|
| Server order row | partner_order_id, user reference, side, amount, asset, network, destination reference. | Lets product, support, and finance speak about the same request. |
| 0Pools quote row | pool_id, quote_id, quote type, expiry, source amount, target amount, fee/spread fields returned to the partner. | Proves exactly what the user accepted. |
| Execution row | transact_id, idempotency_key, execution attempt timestamp, current status. | Prevents duplicate accepts and makes retries explainable. |
| Event row | webhook/event id, signature verification result, received timestamp, processed timestamp. | Proves async delivery and replay handling. |
| Ledger row | debit/credit amount, currency, ledger reference, terminal status. | Gives finance a closeable record. |
Minimal server flow
async function acceptPoolQuote(order: PartnerOrder) {
const idempotencyKey = `${order.id}:accept:${order.acceptVersion}`;
const quote = await pools.createFirmQuote({
poolId: order.poolId,
side: order.side,
sourceAmount: order.sourceAmount,
destination: order.destinationRef,
});
await db.poolQuotes.insert({
orderId: order.id,
quoteId: quote.quoteId,
expiresAt: quote.expiresAt,
acceptedTerms: quote,
});
const trade = await pools.transact({ quoteId: quote.quoteId }, { idempotencyKey });
await db.poolTrades.upsert({
orderId: order.id,
quoteId: quote.quoteId,
transactId: trade.transactId,
status: trade.status,
idempotencyKey,
});
return trade;
}Acceptance tests before launch
| Test | Expected result |
|---|---|
| Execute the same accepted quote twice with the same idempotency key. | One trade record is returned; no duplicate user debit. |
Execute after expiresAt. | The backend rejects or requests a fresh quote; the UI never silently refreshes accepted terms. |
| Drop the webhook once and poll status. | The ledger still reaches the correct terminal state. |
| Send a malformed destination/network combination. | The error is mapped to user-safe copy and support keeps the request id. |
Implementation depth
This page is about launching the first server-side 0Pools flow. For developers, the durable boundary is simple: your product should depend on entitled pools, capabilities, quote ids, trade ids, statuses, and ledger joins; it should not depend on route choice, venue balances, reserve allocation, and treasury movement. That boundary is what lets 0Pools improve liquidity operations without forcing every partner to rebuild product code.
Product scenario
A neobank lets a user buy USDT from EUR inside its own app. The app previews an indicative quote while the user edits the amount, locks a firm quote only on confirmation, executes once from the server, and updates the ledger only from terminal status.
Before and after in practice
| Before 0Pools | With 0Pools |
|---|---|
| The launch checklist lives across Slack, spreadsheets, and partial vendor dashboards. A duplicate click or expired price can create an investigation before anyone has a common id chain. | The partner stores pool id, quote id, transact id, status, request id, and ledger row from the same lifecycle, so support and finance can reconstruct the order from one record chain. |
| Product teams infer liquidity behavior from provider-specific screens or manual notes. | Product teams branch on 0Pools objects: pool, quote, trade, balance, webhook, and ledger. |
| Support asks engineering to reconstruct state from logs. | Support starts from request id, quote id, trade id, event id, and ledger id. |
System flow
Records to keep
| Record | Why it matters | Example |
|---|---|---|
partner_order_id | Connects your product order to 0Pools state. | order_7841 |
quote_id | Explains exactly which price was accepted. | pq_test_123 |
transact_id | Connects execution, webhooks, and reconciliation. | pt_test_456 |
idempotency_key | Protects the accept button and server retries. | order_7841_accept_1 |
A useful implementation stores these records before adding optional analytics. Analytics can be rebuilt from durable state; missing ids usually cannot be reconstructed after a customer-impacting failure.
Example product record
{
"product_area": "quickstart",
"partner_reference": "partner_order_123",
"pool_id": "EUR-USDT",
"quote_id": "pq_test_123",
"transact_id": "pt_test_456",
"status": "reserved",
"request_id": "req_01HZY0POOLS",
"reconciliation_state": "open"
}This record is intentionally partner-facing. It references 0Pools objects and your own product ids, but it does not include private liquidity routes, treasury balances, raw provider payloads, or customer secrets.
Failure modes and recovery
| Failure mode | Product response |
|---|---|
| Expired firm quote | Create a fresh quote and ask the user to confirm again. |
| Duplicate execute request | Reuse the same idempotency key and render the returned trade. |
| Unavailable pool | Hide or disable the lane until discovery/capabilities show it as available. |
| Reserved too long | Keep the user pending and reconcile from status/webhook before final ledger credit. |
Use-case patterns
| Pattern | How 0Pools helps |
|---|---|
| Wallet top-up | User buys stablecoin for a self-custody wallet. |
| Neobank crypto tab | Banking app exposes crypto buy flow without becoming a liquidity desk. |
| Card program funding | A card product funds a crypto balance with bounded quote acceptance. |
Developer checklist
- Read runtime discovery and capabilities before rendering enabled actions.
- Create firm quotes only when the user is ready to accept the terms.
- Execute from your server with an idempotency key.
- Treat webhooks and status reads as the source of truth for settlement state.
- Reconcile by durable ids first, then by amount and timestamp.
- Do not branch product behavior on provider names, route names, reserve balances, or treasury assumptions.