OTC
All endpoints require
Authorization: Bearer <token>and thex-client-id: <Client_ID>header — your tenant id is the same value as your OAuthClient_ID(details); without it requests fail with400 MISSING_CLIENT_ID. Operations are scoped to your tenant.
Overview
The OTC API lets your tenant request and execute crypto buy/sell trades (e.g. USDT/BRL) entirely via API. Liquidity, the provider and our settlement account are resolved on our side — you never see the source, the cost or the margin; only the final price applicable to your tenant.
Sides:
BUY(onramp) — you buy crypto and pay in BRL. The crypto is sent to thewalletAddressyou provide.SELL(offramp) — you sell crypto and receive BRL. The crypto leaves a custodial wallet of yours (fromAddress, managed by us); we move it to our custody and settle the BRL.
Fiat leg — how BRL moves
- BUY (onramp): you choose in the request, via
paymentMethod:ACCOUNT_DEBIT(default) — the platform automatically debits, via PIX, the OTC account you have configured (account → our settlement account). No QR, no manual payment. The OTC account must be previously funded in BRL.PIX_QRCODE— we issue a PIX charge (QR code / copy-and-paste) and return it inpixIn. You pay it from an account of your tenant's own company document (CNPJ), at any bank, before it expires. See PIX via QR code.
- SELL (offramp): after the crypto is sold, the BRL is paid via PIX to your tenant's PIX key — the key configured for you by the Axia operator (default) or the one you send in
pixKey. We only pay keys owned by your tenant's CNPJ; a third-party key (including an end customer's) is refused.
CNPJ ownership. Both the PIX you pay us (BUY via QR) and the PIX we pay you (SELL) are bound to the company document (CNPJ) registered for your tenant. It is required to use
PIX_QRCODEand to receive SELL payouts via PIX — confirm with support that your tenant's CNPJ and PIX key are registered.
Price
- BUY: the quote price is indicative. The final price is locked the moment the PIX is confirmed (with
ACCOUNT_DEBIT, seconds after the debit; withPIX_QRCODE, when your payment arrives) — you receive the crypto at the price of that instant. - SELL: the quote (
quoteId) locks the price and execution runs against it.
Typical flow:
- Quote (
GET /v1/otc/quote) — price + resulting volume/amount (onBUY, indicative). - Request the trade (
POST /v1/otc/trades). - Track (
GET /v1/otc/trades/{id}) thestatusuntilSETTLED.
GET /v1/otc/quote
Returns the applicable price, the resulting volume/amount and (on SELL) a locked quoteId.
Query params:
| Param | Type | Required | Description |
|---|---|---|---|
coin | string | Yes | Asset, e.g. USDT |
side | string | Yes | BUY | SELL |
mode | string | Yes | BY_FIAT (you fix the BRL) | BY_CRYPTO (you fix the asset volume) |
amount | number | Yes | BRL when BY_FIAT; asset volume when BY_CRYPTO |
Response 200 OK:
{
"data": {
"side": "BUY",
"mode": "BY_FIAT",
"asset": "USDT",
"price": 5.5825,
"clientRate": 5.5825,
"volume": 17912.5,
"fiatAmount": 100000,
"quoteId": "liq-9f2c...",
"expiresAt": "2026-06-18T12:00:30.000Z"
}
}| Field | Description |
|---|---|
price / clientRate | Price per asset unit (BRL) applicable to your tenant. On BUY it is indicative (the final price is locked at the debit confirmation). |
volume | Asset quantity |
fiatAmount | Total amount in BRL |
quoteId | Quote identifier. On SELL, pass it to POST /v1/otc/trades to execute against the locked price. |
expiresAt | When the locked quote expires (ISO 8601). |
The price is provider-agnostic: you never see the liquidity source, the cost or the margin.
POST /v1/otc/trades
Creates the trade. Execution is asynchronous (in the background) and idempotent — track status until SETTLED.
Headers:
| Header | Value |
|---|---|
Authorization | Bearer <token> |
Content-Type | application/json |
Request Body (BUY — onramp):
{
"asset": "USDT",
"side": "BUY",
"mode": "BY_FIAT",
"amount": 100000,
"walletAddress": "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"walletNetwork": "TRON"
}Without paymentMethod (or with ACCOUNT_DEBIT), the platform automatically debits your OTC account via PIX and delivers the USDT to the walletAddress — there is no pixIn/QR to pay.
Request Body (BUY — onramp, paid via QR code):
{
"asset": "USDT",
"side": "BUY",
"mode": "BY_FIAT",
"amount": 100000,
"walletAddress": "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"walletNetwork": "TRON",
"paymentMethod": "PIX_QRCODE"
}The response carries pixIn with the copy-and-paste payload to be paid from your tenant's CNPJ before pixIn.expiresAt — see PIX via QR code.
Request Body (SELL — offramp):
{
"asset": "USDT",
"side": "SELL",
"mode": "BY_CRYPTO",
"amount": 18000,
"quoteId": "liq-9f2c...",
"fromAddress": "TYyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"walletNetwork": "TRON",
"pixKey": "[email protected]"
}| Field | Type | Required | Description |
|---|---|---|---|
asset | string | Yes | Traded asset (e.g. USDT). Also accepts coin. |
side | string | Yes | BUY | SELL |
mode | string | Yes | BY_FIAT | BY_CRYPTO |
amount | number | Yes | BRL (BY_FIAT) or volume (BY_CRYPTO) |
walletAddress | string | BUY | BUY: destination address that will receive the crypto (required). SELL: not used — provide fromAddress. |
walletNetwork | string | Yes | Delivery network: TRON (USDT-TRC20) or ETH (USDT-ERC20). See Delivery networks. |
fromAddress | string | SELL | SELL: source address — custodial wallet of your tenant (created via POST /v1/crypto/wallets), not blocked. |
pixKey | string | No (SELL) | SELL: PIX key that receives the BRL. Its owner must be your tenant's CNPJ (checked against the DICT registry at creation and again before payout; a third-party key → 403). Absent → the PIX key configured for your tenant is used; with none, the key of your registered OTC account. |
paymentMethod | string | No (BUY) | BUY: ACCOUNT_DEBIT (default — automatic debit of your OTC account) or PIX_QRCODE (we issue a QR and you pay it from your tenant's CNPJ). Invalid value → 400 INVALID_PAYMENT_METHOD. Ignored on SELL. |
quoteId | string | No | SELL: locked quote (from GET /v1/otc/quote). BUY: optional/indicative — the final price is locked at confirmation. |
bankingAccountId | string | No | Tenant account; defaults to the most recent active account. |
Response 200 OK:
{
"data": {
"id": "a1b2c3d4-...",
"side": "BUY",
"asset": "USDT",
"quoteCurrency": "BRL",
"volume": 17912.5,
"unitPrice": 5.5825,
"totalAmount": 100000,
"walletAddress": "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"walletNetwork": "TRON",
"status": "PROCESSING",
"txHash": null,
"createdAt": "2026-06-18T12:00:00.000Z",
"executedAt": null
}
}BUY (onramp): with
ACCOUNT_DEBITthe fiat leg is an automatic PIX debit from your OTC account — don't send anything manually. WithPIX_QRCODEthe response includespaymentMethod: "PIX_QRCODE"and thepixInblock to pay. The final price is locked at the PIX confirmation. SELL (offramp): you don't send the crypto manually. Since the source wallet is custodied by us, we initiate the on-chain transfer from it to our custody and proceed with settlement.
Common errors:
| HTTP | Reason |
|---|---|
400 | Missing fields / missing clientId / amount outside ticket limits / fromAddress missing on SELL |
403 | fromAddress (SELL) does not belong to your tenant, or the wallet is blocked; PIX key refused — the pixKey (SELL) is not owned by your tenant's CNPJ |
404 | No quote for the asset / no active bank account for the tenant |
409 | QUOTE_EXPIRED (quote expired — quote again), SLIPPAGE_EXCEEDED (price moved outside tolerance — quote again) or tenant CNPJ not registered (required for PIX_QRCODE) |
429 | Rate-limit exceeded |
PIX via QR code (BUY)
With paymentMethod: "PIX_QRCODE", instead of debiting your OTC account we issue a PIX charge on our settlement account and return the data for you to pay. The trade stays PENDING until the PIX arrives.
Response 200 OK (excerpt):
{
"data": {
"id": "a1b2c3d4-...",
"side": "BUY",
"status": "PENDING",
"paymentMethod": "PIX_QRCODE",
"totalAmount": 100000,
"pixIn": {
"emv": "00020126580014br.gov.bcb.pix...",
"pixKey": "[email protected]",
"transactionIdentification": "a1b2c3d4",
"amount": 100000,
"expiresAt": "2026-08-27T17:30:00.000Z",
"payerDocument": "12345678000199",
"paid": false
}
}
}| Field | Description |
|---|---|
pixIn.emv | QR payload (PIX copy-and-paste). Render it as a QR code or paste it in the banking app. |
pixIn.amount | Exact BRL amount to pay. A payment with a different amount is not reconciled. |
pixIn.expiresAt | Payment deadline. With no PIX by then, the trade becomes FAILED (PIX_IN_EXPIRED). |
pixIn.payerDocument | The CNPJ that must be the payer (the document registered for your tenant). |
pixIn.paid | true once the PIX has been reconciled. |
Rules:
- The PIX must come from an account of the CNPJ in
payerDocument(any bank). A PIX paid by another document is refused: the trade becomesFAILED(PIX_IN_PAYER_MISMATCH), no crypto is delivered and the amount is refunded manually by our team — contact support. - Pay the exact amount and within the deadline.
pixInis also returned byGET /v1/otc/trades/{id}while the trade is pending. - Once the PIX is confirmed the price is locked and execution proceeds as usual (
PROCESSING→SETTLED); use theotc.trade.settled/otc.trade.failedwebhooks or polling.
GET /v1/otc/trades
Lists your tenant's trades (only those originated by your clientId).
Query params: limit, offset (optional).
Response 200 OK: array of trades (same shape as POST).
GET /v1/otc/trades/{id}
Trade detail. Returns 404 if the trade does not belong to your tenant.
Response 200 OK:
{
"data": {
"id": "a1b2c3d4-...",
"side": "SELL",
"asset": "USDT",
"totalAmount": 100000,
"walletAddress": "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"walletNetwork": "TRON",
"status": "SETTLED",
"txHash": "0xabc...",
"createdAt": "2026-06-18T12:00:00.000Z",
"executedAt": "2026-06-18T12:03:00.000Z"
}
}Trade states (status)
| Status | Meaning |
|---|---|
PENDING | Received, not started yet |
PROCESSING | In processing (fiat debit and/or crypto settlement in progress) |
SETTLED | Completed |
FAILED | Failed |
CANCELLED | Cancelled |
States are neutral by design — you track the trade's macro-state without seeing how liquidity was sourced. When status is SETTLED, txHash carries the on-chain hash (crypto send on BUY; custody transfer on SELL).
Webhooks (alternative to polling): instead of polling
GET /v1/otc/trades/{id}untilSETTLED, you can subscribe to theotc.trade.settledandotc.trade.failedevents and receive the final trade by push. See Webhooks.
Delivery networks
USDT can be delivered on two networks, selected via the walletNetwork field of the trade:
| Network | walletNetwork | Address format | Example |
|---|---|---|---|
| TRON (TRC-20) | TRON | Base58, starts with T, 34 chars | TXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
| Ethereum (ERC-20) | ETH (or ETHEREUM) | Hex, starts with 0x, 40 chars | 0xAbC1230000000000000000000000000000000000 |
Address validation. The walletAddress (BUY) / fromAddress (SELL) is validated against the network before any movement: an address from another network (e.g. a T... on an Ethereum delivery, or a 0x... on a TRON delivery) or a malformed address is rejected with 400. This protects against loss of funds from delivery on the wrong network.
Per-network cost. The Ethereum network has a higher, variable network fee (gas); the final price applicable to your tenant already reflects the chosen network. On-chain confirmation on Ethereum may also take longer than on TRON.
The accepted networks are TRON and Ethereum. Any other network returns
400.
Withdrawal whitelist (BUY)
By default, a BUY order delivers crypto to the walletAddress provided in the request. Optionally, you can restrict delivery to previously registered addresses: enable the whitelist and register the allowed addresses. While enabled, a BUY to an address not in the list is rejected with 403. The whitelist does not apply to SELL (whose address is your source custody wallet).
All endpoints are scoped to your tenant (clientId from OAuth). The toggle can also be turned on/off by the Axia operator.
GET /v1/otc/whitelist/settings
Returns { "enabled": true|false }.
PUT /v1/otc/whitelist/settings
Enable/disable the whitelist. Body: { "enabled": true }.
GET /v1/otc/whitelist
Lists the registered addresses: [{ "id", "network", "address", "label" }].
POST /v1/otc/whitelist
Registers an address. Body: { "network": "TRON", "address": "T...", "label": "Treasury" }. The address is validated per network (TRON/EVM). An already-registered address returns 409.
DELETE /v1/otc/whitelist/{id}
Removes an address from your whitelist.