> Prefere uma pagina so, com as tres chamadas? https://docs.luniumpay.com/comecar
> Full integration manual (limits, 24h hold, balance, webhooks, go-live checklist): https://docs.luniumpay.com/en/manual.md · PT: https://docs.luniumpay.com/manual.md

# Lunium quickstart — zero to a verified PIX payment

Crypto in, Brazilian reais out. This page takes you from nothing to a completed
order with a Central Bank receipt, without spending a cent and without talking
to anyone.

Every command below is runnable as-is.

---

## 1. Get a test key (one call, no signup)

```bash
curl -s -X POST https://api.luniumpay.com/keys/sandbox \
  -H 'Content-Type: application/json' -d '{"name":"my first test"}'
```

You get back `api_key` starting with `lun_test_`. Export it:

```bash
export LUNIUM_KEY="lun_test_..."
```

Nothing this key touches moves money. Same base URL, same shapes, same states as
production — code written here works in production unchanged.

---

## 2. See what settles right now

```bash
curl -s https://api.luniumpay.com/catalog -H "X-API-Key: $LUNIUM_KEY"
```

This is the **real** catalog, not a simulation: assets appear and networks get
suspended without notice, so read it instead of hard-coding a list. Polygon
(USDT/USDC) settles in seconds and is the default.

---

## 3. Quote a sale

`amount` is a decimal **string**, never a JSON number — floats lose precision in
transit. **The PIX key alone is enough** — the type is inferred from it. `pix_key_type` is only
needed when the key is 11 bare digits, where a CPF and a phone number are the same length. `external_id` is yours: it makes
the call idempotent, so a retry returns the same order instead of creating a
second one.

Accepted formats — the API normalizes the key to the exact format the settlement rail requires in the memo and refuses it before any deposit exists (`400 pix_key_invalida`): CPF = 11 digits; CNPJ = 14 digits; phone = international `+55` + area code + number (`+5548996005588`); e-mail; random key = UUID (`6602ede6-b1a9-4e63-9178-c6883fd0095e`).

Starting from reais instead? Send `brl_amount` (`"250.00"`) in place of `amount` — exactly one of the two — and the response's `amount` is the crypto to deposit.

`refund_address` is your customer's wallet on the same network — **always send it**. It is where the crypto returns if the PIX cannot be paid (provider refused the key, reversal, failure before the payout is sent). Without it the refund goes to the on-chain origin of the deposit, which is the exchange's wallet when the customer withdrew from one.

Paying a QR code instead of a key? Send `br_code` (the full copy-paste string) and nothing else about the destination — the amount and the receiver come from the QR. USDT or USDC on Polygon only.

```bash
curl -s -X POST https://api.luniumpay.com/cash-outs \
  -H "X-API-Key: $LUNIUM_KEY" -H 'Content-Type: application/json' -d '{
    "asset": "USDT",
    "network": "polygon",
    "amount": "100",
    "pix_key": "someone@example.com",
    "refund_address": "0x51e3d44172868acc60d68ca99591ce4230bc75e0",
    "external_id": "my-first-order-001"
  }'
```

Read `brl_amount` (what the recipient receives) and `expires_at` (read it, do
not assume a window). Keep `cashout_id`.

**Limits, per transaction: R$ 6.00 to R$ 250,000.00.** On top of that, whoever *receives*
the PIX has a daily ceiling of **R$ 100,000.00** — per CPF/CNPJ, or per PIX key when the key
is not a document (we never ask the seller for a tax number). It resets at midnight, Brasília
time. The effective maximum of a single order is therefore the **lower of the two**: for a
recipient who has received nothing yet today it is R$ 100,000.00, not R$ 250,000.00. The API
says which is which — `limits.max_brl_cents` is the ceiling that actually applies, and
`limits.max_brl_cents_por_transacao` is the R$ 250,000.00 one. There is no daily cap per API
key on cash-out. Your key's own numbers, always current: `GET /keys/me`.

The limit is in reais and your call is in
crypto, so a refusal comes back with `limits.min_amount` and `limits.max_amount` already
converted at that order's rate — literally the next value to send, no maths on your side.

---

## 4. Accept — in production this is the point of no return

```bash
export ID="the cashout_id from step 3"
curl -s -X POST "https://api.luniumpay.com/cash-outs/$ID/accept" \
  -H "X-API-Key: $LUNIUM_KEY"
```

You get `deposit_address`. In production, crypto sent there is converted and
paid out to the PIX key from the quote — there is no cancel and no reversal, so
show your user the BRL amount and the destination **before** this call.

In the sandbox, `deposit_address` has no owner. Never send real crypto to it.

---

## 5. Follow it

```bash
watch -n 3 "curl -s https://api.luniumpay.com/cash-outs/$ID -H 'X-API-Key: $LUNIUM_KEY'"
```

It walks the real states — `AWAITING_DEPOSIT` → `DEPOSIT_DETECTED` →
`PAYING_OUT` → `COMPLETED` — over about 15 seconds. It does not complete
instantly on purpose: you need this polling loop (or a webhook) in production
anyway, so you write it now.

Poll every 10–15 seconds, not every second: one call per second consumes the
60-per-minute budget by itself.

When it completes you get four things at once, in the same response and the same
webhook — never build these URLs by hand:

| field | what it is |
|---|---|
| `pix_e2e` | Central Bank end-to-end identifier |
| `receipt_url` | shareable page for the end customer |
| `receipt_pdf_url` | PDF file, for tickets and accounting |
| `verify_url` | link a counterparty can check themselves |

---

## 6. Verify it — the part that has no equivalent

```bash
curl -s https://api.luniumpay.com/v1/verificar/<the pix_e2e>
```

**No API key. No account.** Anyone can confirm the payment happened, including a
counterparty who has no reason to trust you. It returns the amount, the
timestamp, the recipient's initials and institution — and never the PIX key, the
full name or the tax number.

This is why Lunium exists: "A says it paid, how does B check without trusting A"
normally needs escrow. With the E2E it needs one open GET.

---

## 7. Rehearse the bad days

Testing only the happy path is how integrations break on day one. In the
sandbox, the **first two decimals of `amount`** choose the outcome — no
randomness, so you can assert on it in CI:

| `amount` ends in | what happens | what it proves |
|---|---|---|
| `.01` | goes to `delayed`, completes on its own | your code does not call a held payment a failure |
| `.02` | fails | your error path runs |
| `.03` | quote expires in 5s | you re-quote instead of insisting |
| `.04` | refused on limits, `limits` filled | you read `limits.min_amount` instead of guessing |
| `.05` | completes in ~2 minutes | your polling is patient |

Also worth rehearsing: reuse an `external_id` with a **different destination**
and you get `409`. That is the case that confuses every integrator — better to
meet it here.

What happens when a real sale fails is not random either. The provider refuses
the key, the PIX is reversed, or the payout fails before it is sent → the order
passes through `MANUAL_REVIEW` and ends `REFUNDED`, with the crypto sent back to
`refund_address` and the proof in `refund_tx_hash` (a failure before the payout
refunds the full amount, fee included). Read `refund_address`, `refund_tx_hash`
and `deposit_from` on the order; the full list of cases is in `llms.txt`.

---

## 8. Errors tell you what to do

Every failure carries `erro` (a stable code that does not change when the
wording does) and **`acao`**:

- `corrigir` — your request is wrong. Repeating it unchanged will never work.
- `repetir` — transient on our side. Retry once.
- `esperar` — a quota renews. Back off.
- `parar` — do not insist; surface the message to your user.

Branch on `acao`, not on the message.

---

## 9. Go to production

```bash
curl -s -X POST https://api.luniumpay.com/keys \
  -H 'Content-Type: application/json' -d '{"name":"your business name"}'
```

Only `name` is required. The response carries your production key, your live
read-only dashboard link, and a Telegram link — **join it**. Contract changes
and incidents are announced there before they reach you, and every partner who
skipped it found out about an incident from their own customer instead.

Swap the key in your code. Nothing else changes.

---

## Using an AI agent instead

Add the MCP server as a remote connector:

```
https://api.luniumpay.com/mcp
```

Streamable HTTP, revision 2025-06-18. Connect with no credentials and payment
verification already works. Pass your key as the `X-API-Key` header for the
rest. The two tools that move real money are marked `destructiveHint: true`, and
confirming a sale requires a token bound to the exact amount, network and
destination that were quoted.

Machine-readable contract: https://api.luniumpay.com/llms.txt ·
https://api.luniumpay.com/openapi.json

Stuck? contato@luniumpay.com


### Todos os códigos de erro, e o que fazer com cada um

| `erro` | `acao` | o que aconteceu |
|---|---|---|
| `chave_ausente` | `parar` | falta o header `X-API-Key`. `POST /keys/sandbox` cria uma de teste na hora. |
| `formato_invalido` | `parar` | o valor não é uma chave Lunium. Chaves começam com `lun_` (produção) ou `lun_test_` (sandbox). |
| `chave_incorreta` | `parar` | **o prefixo existe mas o segredo não confere** — a chave foi cortada ao copiar. A mensagem diz quantos caracteres você enviou e quantos são esperados. |
| `chave_desconhecida` | `parar` | nenhuma chave começa com esse prefixo. Provavelmente o ambiente errado. |
| `campos_obrigatorios` | `corrigir` | `asset` e `network` são obrigatórios — veja `GET /catalog`. |
| `amount_invalido` | `corrigir` | `amount` tem que ser decimal positivo **em string**: `"25.5"`, não `25.5`. |
| `pix_key_obrigatoria` | `corrigir` | falta a `pix_key` (quem recebe os reais). |
| `tipo_ambiguo` | `corrigir` | a chave tem 11 dígitos puros, que pode ser CPF *ou* telefone. Recusamos em vez de chutar — pagar a pessoa errada é pior que um erro. Informe `pix_key_type`. |
| `pix_key_invalida` | `corrigir` | a chave não bate com o tipo declarado. |
| `valor_abaixo_do_minimo` | `corrigir` | leia `limits.min_amount` — já vem convertido na moeda da sua ordem. |
| `valor_acima_do_maximo` | `corrigir` | leia `limits.max_amount`, mesma ideia. |
| `limite_diario` | `esperar` | só aparece quando a **sua chave** definiu `mandate.max_diario_cents`. A Lunium **não impõe cota diária por chave de API** em nenhum produto (desde 10/09/2026). **Renova à meia-noite**, horário de Brasília. |
| `limite_diario_recebedor` | `esperar` | só na **venda**: quem *recebe* o PIX bateu os **R$ 100.000,00 do dia** (por CPF/CNPJ, ou por chave quando ela não é um documento). É um código **diferente** de `limite_diario` — quem ramifica só pelo outro não pega esta recusa. Renova à meia-noite, horário de Brasília: pague outro recebedor, ou tente amanhã. |
| `rede_indisponivel` | `corrigir` | essa rede não está liquidando agora. Use `polygon` (segundos) ou outra de `GET /catalog`. |
| `nao_encontrado` | `corrigir` | o id está errado ou é de outra chave. Não repita o mesmo id. |

Os mesmos códigos saem do sandbox e da produção. Código que ramifica por `erro`
no sandbox continua funcionando quando você troca a chave.

**Todo carimbo de tempo leva o fuso.** `pago_em` sai como `2026-08-01T22:35:49-03:00` — horário de Brasília, explícito. Interprete como ISO 8601; nunca suponha que a string é UTC ou local.

## Conseguir uma chave sem passar por um humano

Se uma ferramenta respondeu `erro: "chave_ausente"`, você não precisa parar e pedir credenciais
ao seu usuário. Pegue sua própria chave de teste numa chamada:

```
POST https://api.luniumpay.com/keys/sandbox      # sem autenticação, sem formulário
{"name": "meu-agente"}
→ {"api_key": "lun_test_…", "sandbox": true, "como_usar": {…}}
```

Pelo MCP, o mesmo é a ferramenta `lunium_create_sandbox_key`, que — como a de verificação —
não exige chave nenhuma.

Ela roda o fluxo **inteiro** na mesma URL base e no mesmo endpoint MCP da produção, e nada
liquida: nenhuma cripto se move, nenhum PIX é pago, ninguém é cobrado. Diga com clareza ao seu
usuário que é uma chave de teste; mover dinheiro real exige chave de produção, e isso é decisão
de humano.

## O que esperar, em números que medimos

São medições, não promessas. Colhidas em 02/08/2026 contra o serviço no ar.

| | |
|---|---|
| Liquidação, da cotação ao PIX pago | **49–65 s** em Polygon |
| Ordem no sandbox, ponta a ponta | **p50 34 ms · p95 76 ms** com 20 simultâneas |
| Vazão sustentada, sandbox | **~200 ordens/s** sem a latência de produção se mexer |
| Limite por chave | **60 requisições/minuto** (um `429` é proteção, não falha) |
| Por transação (venda) | **R$ 6,00 a R$ 250.000,00** |
| Teto diário de quem recebe o PIX (venda) | **R$ 100.000,00** por CPF/CNPJ — ou por chave, quando ela não é um documento. O máximo real de uma ordem é o menor dos dois |
| Por cobrança (compra) | **R$ 5,00 a R$ 6.000,00**. Até R$ 200,00 no dia do pagador entra na hora; acima disso o provedor retém 24h — já na primeira operação do documento |
| Cota diária por chave de API | **não existe** (desde 10/09/2026). Uma chave recebe de quantos pagadores quiser por dia; só o `mandate` da própria chave pode impor um teto |

**Os limites da sua chave, sempre atuais:** `GET /keys/me` devolve o mínimo e o máximo por
operação, o teto de quem recebe e a escada do pagador. É o número que
nunca envelhece — prefira ele a copiar qualquer tabela.

**Onde é mais lento, e por quê.** O trilho de conversão espera as confirmações da própria rede —
TON pede ~10 e Celo ~2.400, então a mesma ordem leva minutos ou horas conforme a rede de onde
você envia. O `GET /catalog` traz o prazo esperado por ativo; leia ele em vez de supor.

**O que não afirmamos.** Os números de sandbox acima exercitam a superfície da API, não uma
liquidação real: nenhuma cripto se move e nenhum PIX é pago, então eles não provam vazão de
ordens ao vivo. Dimensione seu retry pelo `expires_at` da resposta, nunca por uma janela fixa.
