Skip to content

OTC ​

All endpoints require Authorization: Bearer <token> and the x-client-id: <Client_ID> header — your tenant id is the same value as your OAuth Client_ID (details); without it requests fail with 400 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 the walletAddress you 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 in pixIn. 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_QRCODE and 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; with PIX_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:

  1. Quote (GET /v1/otc/quote) — price + resulting volume/amount (on BUY, indicative).
  2. Request the trade (POST /v1/otc/trades).
  3. Track (GET /v1/otc/trades/{id}) the status until SETTLED.

GET /v1/otc/quote ​

Returns the applicable price, the resulting volume/amount and (on SELL) a locked quoteId.

Query params:

ParamTypeRequiredDescription
coinstringYesAsset, e.g. USDT
sidestringYesBUY | SELL
modestringYesBY_FIAT (you fix the BRL) | BY_CRYPTO (you fix the asset volume)
amountnumberYesBRL when BY_FIAT; asset volume when BY_CRYPTO

Response 200 OK:

json
{
  "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"
  }
}
FieldDescription
price / clientRatePrice per asset unit (BRL) applicable to your tenant. On BUY it is indicative (the final price is locked at the debit confirmation).
volumeAsset quantity
fiatAmountTotal amount in BRL
quoteIdQuote identifier. On SELL, pass it to POST /v1/otc/trades to execute against the locked price.
expiresAtWhen 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:

HeaderValue
AuthorizationBearer <token>
Content-Typeapplication/json

Request Body (BUY — onramp):

json
{
  "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):

json
{
  "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):

json
{
  "asset": "USDT",
  "side": "SELL",
  "mode": "BY_CRYPTO",
  "amount": 18000,
  "quoteId": "liq-9f2c...",
  "fromAddress": "TYyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
  "walletNetwork": "TRON",
  "pixKey": "[email protected]"
}
FieldTypeRequiredDescription
assetstringYesTraded asset (e.g. USDT). Also accepts coin.
sidestringYesBUY | SELL
modestringYesBY_FIAT | BY_CRYPTO
amountnumberYesBRL (BY_FIAT) or volume (BY_CRYPTO)
walletAddressstringBUYBUY: destination address that will receive the crypto (required). SELL: not used — provide fromAddress.
walletNetworkstringYesDelivery network: TRON (USDT-TRC20) or ETH (USDT-ERC20). See Delivery networks.
fromAddressstringSELLSELL: source address — custodial wallet of your tenant (created via POST /v1/crypto/wallets), not blocked.
pixKeystringNo (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.
paymentMethodstringNo (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.
quoteIdstringNoSELL: locked quote (from GET /v1/otc/quote). BUY: optional/indicative — the final price is locked at confirmation.
bankingAccountIdstringNoTenant account; defaults to the most recent active account.

Response 200 OK:

json
{
  "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_DEBIT the fiat leg is an automatic PIX debit from your OTC account — don't send anything manually. With PIX_QRCODE the response includes paymentMethod: "PIX_QRCODE" and the pixIn block 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:

HTTPReason
400Missing fields / missing clientId / amount outside ticket limits / fromAddress missing on SELL
403fromAddress (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
404No quote for the asset / no active bank account for the tenant
409QUOTE_EXPIRED (quote expired — quote again), SLIPPAGE_EXCEEDED (price moved outside tolerance — quote again) or tenant CNPJ not registered (required for PIX_QRCODE)
429Rate-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):

json
{
  "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
    }
  }
}
FieldDescription
pixIn.emvQR payload (PIX copy-and-paste). Render it as a QR code or paste it in the banking app.
pixIn.amountExact BRL amount to pay. A payment with a different amount is not reconciled.
pixIn.expiresAtPayment deadline. With no PIX by then, the trade becomes FAILED (PIX_IN_EXPIRED).
pixIn.payerDocumentThe CNPJ that must be the payer (the document registered for your tenant).
pixIn.paidtrue 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 becomes FAILED (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. pixIn is also returned by GET /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 the otc.trade.settled / otc.trade.failed webhooks 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:

json
{
  "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) ​

StatusMeaning
PENDINGReceived, not started yet
PROCESSINGIn processing (fiat debit and/or crypto settlement in progress)
SETTLEDCompleted
FAILEDFailed
CANCELLEDCancelled

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} until SETTLED, you can subscribe to the otc.trade.settled and otc.trade.failed events 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:

NetworkwalletNetworkAddress formatExample
TRON (TRC-20)TRONBase58, starts with T, 34 charsTXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Ethereum (ERC-20)ETH (or ETHEREUM)Hex, starts with 0x, 40 chars0xAbC1230000000000000000000000000000000000

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.