On-ramp and off-ramp · Brazil

Brazil on-ramp and off-ramp, in one API

Pix is the country's payment method: available 24/7, and everyone has it. What newcomers lack is not demand — it is the plumbing. That is what this API is.

Create a sandbox key Talk to integration Full documentation

Who this is for

The problem

Everyone arriving in Brazil hits the same sequence: PIX requires an authorised institution, the account requires a local entity, the entity requires partners and time, and converting to stablecoin requires an exchange with its own KYC. That is months before the first transaction — and each part is a separate contract, limit and reconciliation.

The alternative is to treat Brazil as a rail rather than a subsidiary: you talk to an API, money comes in and out in reais, and your balance stays in stablecoin. The teams already doing this integrated by reading the docs, with no meeting.

The flow, end to end

  1. In: POST /cashin/charge creates a PIX QR identified by the payer's tax id. Once paid, the stablecoin goes to your wallet.
  2. Out: POST /cash-outs quotes, /accept locks and returns the address; you deposit and a PIX key receives reais.
  3. Custody (optional): with destino: "saldo" the PIX becomes a BRL balance per sub-account, and you pay out with POST /payouts whenever you want.
  4. Both directions use the same key, the same error shape (erro + acao) and the same external_id.
  5. Every event arrives signed by webhook; nothing requires polling.

Endpoints used

EndpointWhat for
POST /cashin/chargeIn: creates the PIX QR and delivers stablecoin.
POST /cash-outs + /acceptOut: quote, lock and get the deposit address.
POST /payoutsDirect PIX in reais from the custodial balance, no crypto leg.
GET /keys/meYour current limits and what is enabled. Read from here, not from code.
GET /v1/verificar/{e2e}Public verification of a settled PIX. No key.

A example that runs

# entrada: PIX vira stablecoin na sua carteira
curl -X POST https://api.luniumpay.com/cashin/charge -H "X-API-Key: $LUNIUM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_cents":100000,"asset":"usdc","chain":"polygon",
       "payout_address":"0xTesouraria","payer_tax_number":"12345678909",
       "external_id":"in-9001"}'

# saída: stablecoin vira PIX na conta de alguém
curl -X POST https://api.luniumpay.com/cash-outs -H "X-API-Key: $LUNIUM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"asset":"USDC","network":"polygon","brl_amount":"1000.00",
       "pix_key":"11122233344","pix_key_type":"cpf","external_id":"out-9001"}'
const h = { "X-API-Key": process.env.LUNIUM_KEY, "Content-Type": "application/json" };

export const entrada = (cents, carteira, docPagador, id) =>
  fetch(`${API}/cashin/charge`, { method: "POST", headers: h, body: JSON.stringify({
    amount_cents: cents, asset: "usdc", chain: "polygon",
    payout_address: carteira, payer_tax_number: docPagador, external_id: id,
  })}).then((r) => r.json());

export const saida = (brl, chave, tipo, id) =>
  fetch(`${API}/cash-outs`, { method: "POST", headers: h, body: JSON.stringify({
    asset: "USDC", network: "polygon", brl_amount: brl,
    pix_key: chave, pix_key_type: tipo, external_id: id,
  })}).then((r) => r.json());
import os, requests

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

def entrada(cents, carteira, doc_pagador, ident):
    return requests.post(f"{API}/cashin/charge", headers=H, timeout=30, json={
        "amount_cents": cents, "asset": "usdc", "chain": "polygon",
        "payout_address": carteira, "payer_tax_number": doc_pagador,
        "external_id": ident}).json()

def saida(brl, chave, tipo, ident):
    return requests.post(f"{API}/cash-outs", headers=H, timeout=30, json={
        "asset": "USDC", "network": "polygon", "brl_amount": brl,
        "pix_key": chave, "pix_key_type": tipo, "external_id": ident}).json()

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

Every error carries erro (what happened) and acao (what to do): corrigir, repetir, esperar or parar. It is the field an agent or an automatic retry can follow without heuristics.

parar is the important one: on provedor_sem_resposta in a payout, the PIX may have gone out. Look the operation up, do not repeat.

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

Sandbox in one call, no prior approval. The sandbox contract is identical to production — same fields, same states, same errors — so the code you wrote while testing is the code that ships.

Questions integrators ask

Do I need a Brazilian entity?

Not to integrate or to test. What exists is review proportional to volume, as on any payment rail.

How does the limit work when starting out?

The ladder is per paying tax id: BRL 100.00 on the first operation, BRL 200.00 in the first 24 h and BRL 6,000.00/day afterwards. Above that the QR is still accepted, held for 24 h. Because the limit belongs to the payer, many payers add up with no single ceiling.

Do you hold my money?

Only if you ask. In the default flow the stablecoin goes straight to your wallet and the PIX straight to the destination key. BRL custody is a separate product, via destino: "saldo".

Can an AI agent use it?

Yes: besides REST there is MCP, an Agent Card and llms.txt. Every error carries machine-readable guidance, and the PIX verifier is open.

Create a sandbox key Talk to integration

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

In and out of Brazil. 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.BRL ↔ USDT over APIBoth directions, with a quote and a receipt.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