BRL ↔ USDT · production

BRL ↔ USDT over API

Both directions on one key, with a price you can read before any commitment. The quote is a call that creates no order, consumes no limit and answers with the same math that will apply.

Create a sandbox key Talk to integration Full documentation

Who this is for

The problem

Converting BRL and USDT predictably has two enemies: a price that moves between decision and execution, and a process that depends on someone logging in somewhere. The first becomes a silent loss; the second becomes a dependency on business hours.

The answer is to separate quoting from executing. The preview answers with the same math as the real order — same rail, same minimums, same refusals — but persists nothing. You show the number, the user decides, and only then does the order come into being with the amount locked.

The flow, end to end

  1. Quote: POST /cashin/preview (reais → crypto) or POST /cash-outs/preview (crypto → reais). Nothing is created.
  2. The preview returns the rail (path), the quantity and, on the buy side, the route minimum in reais.
  3. Buy: POST /cashin/charge generates the PIX; once paid, the crypto goes to the named wallet.
  4. Sell: POST /cash-outs + /accept returns the address; once deposited, the PIX goes out.
  5. Every operation has an external_id, a signed webhook and a receipt — reconciliation is automatic.

Endpoints used

EndpointWhat for
POST /cashin/previewBuy price and the route minimum in reais. Creates nothing.
POST /cash-outs/previewSell price both ways, with no order and no limit consumed.
POST /cashin/chargeBuy: generates the identified PIX and delivers the crypto.
POST /cash-outs + /acceptSell: locks the quote and returns the deposit address.
GET /keys/mePer-operation limits and your key's current ranges.

A example that runs

# preço antes de qualquer ordem — os dois sentidos
curl -X POST https://api.luniumpay.com/cashin/preview -H "X-API-Key: $LUNIUM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_cents":100000,"asset":"usdt","chain":"polygon"}'

curl -X POST https://api.luniumpay.com/cash-outs/preview -H "X-API-Key: $LUNIUM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"asset":"USDT","network":"polygon","amount":"1000"}'
const h = { "X-API-Key": process.env.LUNIUM_KEY, "Content-Type": "application/json" };

// BRL -> USDT: quanto de cripto sai por R$ X (com o mínimo da rota)
export async function comprar(cents) {
  const p = await (await fetch(`${API}/cashin/preview`, { method: "POST", headers: h,
    body: JSON.stringify({ amount_cents: cents, asset: "usdt", chain: "polygon" }) })).json();
  return { usdt: p.usdt_amount ?? p.amount, minimoBrlCents: p.min_brl_cents };
}

// USDT -> BRL: quanto de PIX sai por N USDT, sem criar ordem
export async function vender(qtd) {
  const p = await (await fetch(`${API}/cash-outs/preview`, { method: "POST", headers: h,
    body: JSON.stringify({ asset: "USDT", network: "polygon", amount: String(qtd) }) })).json();
  return { brl: p.brl_amount, trilho: p.path };
}
import os, requests

API, H = "https://api.luniumpay.com", {"X-API-Key": os.environ["LUNIUM_KEY"]}

def comprar(cents):
    p = requests.post(f"{API}/cashin/preview", headers=H, timeout=30, json={
        "amount_cents": cents, "asset": "usdt", "chain": "polygon"}).json()
    return p.get("usdt_amount") or p.get("amount"), p.get("min_brl_cents")

def vender(qtd):
    p = requests.post(f"{API}/cash-outs/preview", headers=H, timeout=30, json={
        "asset": "USDT", "network": "polygon", "amount": str(qtd)}).json()
    return p["brl_amount"], p["path"]

Sandbox: test before spending

One call, no prior approval, no card; only your email is required (that is where we send the manual, the flowchart and the integration steps). The lun_test_ key exercises the whole surface without moving any money.

curl -X POST https://api.luniumpay.com/keys/sandbox \
  -H "Content-Type: application/json" \
  -d '{"email":"you@company.com","name":"my-test"}'

What the sandbox does not prove: real settlement. No crypto moves and no PIX is paid — it is there to get the contract right, not to measure the rail.

Assets and networks

Use GET /catalog for incoming crypto and GET /cashin/catalog for delivery after Pix. Asset availability, networks and minimums differ between the two directions and can change.

Send the returned deposit_tag with a deposit when required. Use payout_tag for an outgoing route that requires a memo. See the API manual for the current contract.

Limits and holds

Read account limits and fees from GET /keys/me, payer availability from GET /cashin/limits?payer_tax=... and route minimums from previews. Cash-in, cash-out and payouts have different constraints.

A received payment can be held before settlement. Track status, timeline and signed events until the final outcome; read expiry from expires_at.

Measurements, not promises

Settlement time depends on the payment flow, route availability, network confirmations and any hold. Track the final state and check service status. Sandbox timing does not measure real settlement.

Idempotency

Send external_id on every money-moving call. Repeating the same call returns the same operation, never a second one; the same parameters with a different id create two, and the same id with different parameters answers 409 external_id_divergente instead of guessing.

A timeout is not a refusal. If the call did not answer, look the operation up by external_id before repeating — it may already exist.

Webhooks and reconciliation

Every transition fires an event signed with HMAC SHA-256 plus a timestamp (header X-Lunium-Signature, format t=…,v1=…) — the timestamp is what stops a captured delivery from being replayed. Verify the signature before trusting the body.

The webhook is the primary signal; polling is optional. When you do poll, the status carries a timeline stamped by the server — draw the stage from it, because your polling stopwatch measures your network, not the operation.

Did not answer 2xx? Delivery is retried with backoff. You can resend by hand at POST /webhooks/deliveries/{event_id}/retry and test your URL at POST /webhooks/test.

When it goes wrong

Below the minimum: the refusal names the number — on the buy side, min_brl_cents comes in the preview itself, so the screen shows the minimum before the user types.

Above the maximum: valor_acima_do_maximo with the current ceiling. Read it from GET /keys/me instead of hard-coding: ceilings change by contract.

Expired quote: the order has an expires_at. After it, the deposit arrives orphaned — quote again rather than depositing late.

Security

Open verification is the one that matters in a negotiation: your counterparty checks the PIX themselves, with no key and without taking our word for it.

Time to first integration

The first useful integration is just the preview: one call, no side effects, already enough to put on a screen. Execution comes later, on the same contract you already tested.

Questions integrators ask

Does the preview consume a limit or create an order?

Neither. It runs the same math as the real quote and returns the same rail and the same errors, persisting nothing — it was built for screens that quote on every keystroke.

Is the price locked?

On the sell side, yes: the accept locks the quote and expires_at says how long it holds. On the buy side, the PIX amount is what you set, and the delivered crypto follows the operation's quote.

Which pairs work?

Read the catalog for the selected flow and the operation preview. Routes, minimums and timing depend on current availability; sandbox delivery is simulated.

Can I operate outside banking hours?

Yes. PIX settles 24/7, and the API has no window — what varies is the confirmation time of the chosen network.

Create a sandbox key Talk to integration

Current OpenAPI contract · facts on this page in product-truth.json · status

Convert BRL and USDT. Start with a test key.

The test key takes one call, no prior approval and no cost — and your case goes straight to the integration team.

Get a sandbox key and talk to integration 3.3% + R$ 1.00 on cash-in · 1% + provider fee on PIX → PIX · no monthly fee

Other paths

PIX to USDT APITake reais, deliver USDT to your customer’s wallet.USDT to PIX APISell stablecoin and pay any PIX key.PIX payout APIPay suppliers and users in reais, over the API.Brazil on-ramp and off-rampCrypto in and out of Brazil, in one integration.PIX at checkout, stablecoin in treasuryCharge in PIX, hold in digital dollars.PIX and stablecoin for walletsGive your users a way in and out.Infrastructure under your brandThe whole rail running behind your product.

Talk to the people who built it

A sandbox key takes one call, no prior approval — if that's what you're after, start with the docs. This form is for the rest: volume pricing, a case the docs don't cover, or a question before you integrate.

Rather email us? contato@luniumpay.com