{
  "openapi": "3.1.0",
  "info": {
    "title": "Lunium — crypto ⇄ PIX settlement (Brazil)",
    "version": "1.8.0",
    "summary": "Liquidação entre criptomoedas e PIX no Brasil.",
    "description": "Settlement layer between crypto and PIX, Brazil's instant payment system (Central Bank, 24/7, settles in seconds). Send crypto and a PIX key receives Brazilian reais; or pay a PIX charge and receive crypto. 1,400+ assets.\n\nEvery paid operation returns the Central Bank end-to-end identifier (E2E), a shareable receipt page and a PDF. **Payment verification is open and needs no API key** — `GET /v1/verificar/{e2e}` lets any agent confirm a PIX happened without trusting whoever claims to have paid, and without exposing the parties.\n\nErrors carry `erro` (stable machine code) and `acao` — `corrigir` (the request is wrong, retrying identically never works), `repetir` (transient, retry), `esperar` (a quota renews), `parar` (do not retry).\n\nField names are in Portuguese for backward compatibility with existing integrators; every one of them is documented in English here.\n\n**MCP server** (Model Context Protocol, Streamable HTTP, revision 2025-06-18): `https://api.luniumpay.com/mcp`. It is JSON-RPC, not REST, so it is not described here — connect it as a remote connector and call `tools/list`. It works with no credentials at all: payment verification is immediately usable.\n\n**Sandbox**: `POST /keys/sandbox` gives a test key in one call, no signup — see that operation below.\n\nGuide for agents: https://api.luniumpay.com/llms.txt · Em português: https://api.luniumpay.com/llms.pt.txt\n\n---\n\nUm cliente entrega cripto e uma chave PIX recebe reais (cash-out), ou paga um PIX e recebe cripto na carteira (cash-in). Mais de 1.400 ativos em dezenas de redes. Toda operação sai com comprovante e identificador oficial do Banco Central (E2E).\n\nQuem integra não precisa montar exchange, manter saldo em rede nenhuma nem custodiar reais.\n\n**Ordem do cash-out:** cotar (`POST /cash-outs`) → aceitar (`POST /cash-outs/{id}/accept`, devolve o endereço de depósito) → acompanhar (`GET /cash-outs/{id}`).\n\n**Sempre envie `external_id`**: é a chave de conciliação e torna a chamada idempotente — repetir devolve a mesma ordem em vez de criar outra.\n\n**Leia `expires_at` da resposta** em vez de fixar prazo no código.\n\n**Prazos:** Polygon liquida em segundos. Nas demais redes o prazo é o número de confirmações que a rede exige — de cerca de 1 minuto a algumas horas. Nunca prometa \"instantâneo\" fora da Polygon.\n\n## Compatibilidade\n\nEsta spec e o contrato. O que prometemos:\n\n- **Campos novos podem aparecer a qualquer momento** em respostas — ignore o que nao conhece.\n- **Nenhum campo existente e removido ou renomeado** sem uma versao maior e aviso previo.\n- **Codigos de `erro` novos podem surgir**; trate desconhecido como o `acao` mandar.\n- **Ramifique sempre por `erro` e `acao`, nunca pelo texto de `detail`** — `detail` e para humano e muda sem aviso.\n- Mudancas de contrato sao anunciadas antes no grupo de parceiros no Telegram.\n- `info.version` acompanha esta spec: MINOR para adicoes compativeis, MAJOR para quebra.\n\n## Rate limit\n\nToda resposta autenticada carrega `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`. No `429` vai tambem `Retry-After` (segundos). Leia-os em vez de fixar o intervalo no cliente.",
    "contact": {
      "name": "Lunium",
      "email": "contato@luniumpay.com",
      "url": "https://docs.luniumpay.com"
    }
  },
  "servers": [
    {
      "url": "https://api.luniumpay.com",
      "description": "Produção"
    }
  ],
  "tags": [
    {
      "name": "Cash-out",
      "description": "Cripto entra, PIX sai."
    },
    {
      "name": "Cash-in",
      "description": "PIX entra, cripto sai."
    },
    {
      "name": "Catálogo",
      "description": "O que dá para liquidar agora. Fonte da verdade — leia daqui em vez de fixar lista."
    },
    {
      "name": "Verificação",
      "description": "Confirmar um PIX sem ser cliente e sem chave."
    },
    {
      "name": "Conta",
      "description": "Chave, limites e uso."
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Chave criada em `POST /keys`. Aparece uma única vez — guarde no momento da criação."
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your key. `lun_test_…` is sandbox (moves nothing); `lun_…` is production."
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "description": "Corpo de erro da API. `acao` diz o que FAZER — e mais importante que a mensagem. Os mesmos codigos saem do sandbox e da producao. / (en) API error body. `acao` tells you what to DO; sandbox and production emit the same codes.",
        "properties": {
          "erro": {
            "type": "string",
            "enum": [
              "amount_invalido",
              "brl_amount_invalido",
              "asset_invalido",
              "campos_obrigatorios",
              "cashin_indisponivel",
              "cashout_indisponivel",
              "chave_ausente",
              "chave_desconhecida",
              "chave_incorreta",
              "conflito",
              "corpo_grande_demais",
              "cotacao_expirada",
              "cotacao_indisponivel",
              "documento_invalido",
              "em_processamento",
              "erro_interno",
              "external_id_divergente",
              "formato_invalido",
              "ja_entregue",
              "limite_de_chaves_teste",
              "limite_diario",
              "limite_do_pagador",
              "metodo_nao_permitido",
              "nao_autenticado",
              "nao_encontrado",
              "nao_permitido",
              "nao_processavel",
              "payer_tax_invalido",
              "payer_tax_number_invalido",
              "payout_address_invalido",
              "payout_address_obrigatorio",
              "payout_nao_liberado",
              "payout_recusado",
              "pix_key_invalida",
              "pix_key_obrigatoria",
              "provedor_indisponivel",
              "provedor_sem_resposta",
              "rate_limit",
              "rede_indisponivel",
              "requisicao_invalida",
              "sandbox_sem_cashin",
              "status_invalido",
              "tempo_esgotado",
              "temporariamente_indisponivel",
              "tipo_ambiguo",
              "valor_abaixo_do_minimo",
              "valor_acima_do_maximo",
              "webhook_nao_configurado",
              "webhook_url_invalida",
              "chave_nao_encontrada",
              "limite_consultas",
              "consulta_indisponivel"
            ],
            "description": "Codigo estavel de erro. Ramifique por ele, nunca pelo texto de detail. Esta lista e gerada a partir dos codigos que a API realmente emite."
          },
          "acao": {
            "type": "string",
            "enum": [
              "corrigir",
              "repetir",
              "esperar",
              "parar"
            ],
            "description": "corrigir = o pedido esta errado, repetir identico nunca funciona · repetir = falha transitoria nossa, tente de novo · esperar = cota renova, volte depois · parar = nao repita, fale com a gente."
          },
          "detail": {
            "type": "string",
            "description": "Texto para humano. Nunca ramifique por ele."
          },
          "limits": {
            "type": "object",
            "description": "Presente nos erros de limite, ja convertido na moeda da ordem."
          },
          "docs": {
            "type": "string",
            "format": "uri"
          },
          "request_id": {
            "type": "string",
            "description": "Identificador desta requisicao. Vai tambem no header X-Request-ID e e o que citar no suporte. Se voce enviar X-Request-ID no formato [A-Za-z0-9._:-]{8,64}, ele e preservado de ponta a ponta.",
            "examples": [
              "req_62f05f21b3794830a241"
            ]
          },
          "code": {
            "type": "string",
            "deprecated": true,
            "description": "Alias LEGADO de `erro` — SUNSET: deixa de ser enviado em 2026-11-09. NAO ramifique por ele; use `erro` (canonico). Nunca vem sem `erro`."
          }
        },
        "required": [
          "erro",
          "acao",
          "detail"
        ]
      },
      "CashOut": {
        "type": "object",
        "properties": {
          "cashout_id": {
            "type": "string",
            "format": "uuid"
          },
          "state": {
            "type": "string",
            "enum": [
              "QUOTE_CREATED",
              "AWAITING_DEPOSIT",
              "DEPOSIT_DETECTED",
              "PAYING_OUT",
              "COMPLETED",
              "MANUAL_REVIEW",
              "REFUNDING_CRYPTO",
              "REFUNDED",
              "EXPIRED",
              "FAILED"
            ],
            "description": "`COMPLETED` é o único estado em que o PIX saiu. `MANUAL_REVIEW` significa que uma pessoa está olhando — não repita a ordem."
          },
          "path": {
            "type": "string",
            "enum": [
              "FAST",
              "DEX",
              "CONVERT"
            ],
            "description": "`FAST` liquida em segundos (Polygon). `DEX` converte on-chain antes. `CONVERT` passa por exchange e depende das confirmações da rede."
          },
          "asset": {
            "type": "string",
            "examples": [
              "USDT"
            ]
          },
          "network": {
            "type": "string",
            "examples": [
              "polygon"
            ]
          },
          "amount": {
            "type": "string",
            "description": "Quantidade de cripto, em string decimal. Nunca float."
          },
          "brl_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Amount in Brazilian reais (BRL) the PIX key will receive.\n\n(pt) Reais que a chave PIX vai receber."
          },
          "pix_key": {
            "type": "string",
            "description": "Mascarada na resposta."
          },
          "pix_key_type": {
            "type": "string",
            "enum": [
              "cpf",
              "cnpj",
              "phone",
              "email",
              "random"
            ],
            "description": "Usually omit it: the type is inferred from the key for e-mails, CNPJs, random keys and phones written with the +55 country code. Only required when the key is 11 bare digits, because a CPF and a phone number are the same length and guessing would pay the wrong person. An explicit type always wins over inference."
          },
          "deposit_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Para onde o cliente envia a cripto. Só existe depois do accept."
          },
          "deposit_tag": {
            "type": [
              "string",
              "null"
            ],
            "description": "Memo/tag, quando a rede exige. Ignorar isso faz o depósito se perder."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "refund_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Carteira de retorno informada na cotação — destino de qualquer devolução. null = você não informou e a devolução iria para a origem do depósito (deposit_from).\n\n(en) Return wallet given at quote time — destination of any refund. null = not given; the refund would go to deposit_from."
          },
          "refund_tx_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hash on-chain da DEVOLUÇÃO da cripto ao cliente. Só existe quando a ordem terminou em REFUNDED — é a prova. Falha antes do envio ao provedor devolve o valor cheio (taxa inclusive); recusa/reversão do provedor devolve o que ele devolveu.\n\n(en) On-chain hash of the crypto refund. Present only when the order ended REFUNDED — it is the proof."
          },
          "deposit_from": {
            "type": [
              "string",
              "null"
            ],
            "description": "Endereço que ENVIOU o depósito (lido on-chain). Destino da devolução quando refund_address não foi informado.\n\n(en) Address that sent the deposit (read on-chain). Refund destination when refund_address was not given."
          },
          "br_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Copia e cola informado na cotação (cobrança), quando houve. brl_amount é o valor do QR.\n\n(en) Copy-paste string given at quote time (charge), if any. brl_amount is the QR amount."
          },
          "merchant_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do recebedor lido do QR (cobrança).\n\n(en) Receiver name read from the QR (charge)."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "pix_e2e": {
            "type": [
              "string",
              "null"
            ],
            "description": "Central Bank end-to-end identifier of the completed PIX. Anyone can verify it at GET /v1/verificar/{e2e}, no key required.\n\n(pt) Identificador oficial do PIX no Banco Central. É a prova — devolva ao seu cliente final.",
            "examples": [
              "E37293930202607312006361894c4a82"
            ]
          },
          "pix_paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "receipt_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Shareable receipt page — opens on the end customer's phone.\n\n(pt) Pagina publica compartilhavel do comprovante. Abre no celular do cliente final.",
            "examples": [
              "https://app.luniumpay.com/comprovante/uwscff6ASNS1oUcH2LHbvw"
            ]
          },
          "receipt_pdf_url": {
            "type": [
              "string",
              "null"
            ],
            "nullable": true,
            "format": "uri",
            "description": "Receipt as a PDF FILE: attach to a ticket, send over WhatsApp, keep for accounting. Does not depend on our page being up on the day someone checks. Carries recipient name, tax number, institution, amount, date, E2E and the verification link.\n\n(pt) Comprovante em ARQUIVO PDF. E o que se anexa em chamado, manda no WhatsApp do cliente final e guarda na contabilidade -- nao depende de a pagina estar no ar no dia da conferencia. Traz nome, documento e instituicao do recebedor, valor, data, E2E e o link de verificacao. Gerado da MESMA fonte de dados da pagina: os dois nunca divergem. Vem `null` enquanto a ordem nao chegou no estado que o preenche.",
            "example": "https://app.luniumpay.com/comprovante/qigpnCTx3gM-Ge7hN27Mvw.pdf"
          },
          "verify_url": {
            "type": [
              "string",
              "null"
            ],
            "nullable": true,
            "format": "uri",
            "description": "Direct link to the OPEN verification (no key). Hand it to your counterparty so they can confirm the payment themselves, without trusting you or us. Never exposes PIX key, full name or tax number.\n\n(pt) Link direto da verificacao ABERTA (sem chave). Entregue ao seu contraparte para ele mesmo confirmar o pagamento, sem precisar confiar em voce nem em nos. Nao expoe chave PIX, nome completo nem documento. Vem `null` enquanto a ordem nao chegou no estado que o preenche.",
            "example": "https://api.luniumpay.com/v1/verificar/E3729393020260731214021222ac6ff1"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EventoWebhook": {
        "type": "object",
        "description": "Corpo enviado por nos ao seu endpoint. Trate eventos desconhecidos como no-op: a lista cresce sem aviso e ignorar o que nao conhece e o comportamento correto.",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "cashin.paid",
              "cashin.delayed",
              "cashin.settled",
              "cashin.settlement_failed",
              "cashin.expired",
              "cashin.refunded",
              "cashin.failed",
              "cashout.awaiting_deposit",
              "cashout.deposit_detected",
              "cashout.processing",
              "cashout.under_review",
              "cashout.refunding",
              "cashout.completed",
              "cashout.refunded",
              "cashout.expired",
              "cashout.failed",
              "payout.sent",
              "payout.failed",
              "payout.refunded",
              "webhook.test"
            ],
            "description": "Ramifique por aqui. `cashin.settled` e o evento de creditar seu cliente."
          },
          "event_id": {
            "type": "string",
            "pattern": "^evt_[0-9a-f]{24}$",
            "description": "Identidade ESTAVEL do evento — a mesma em toda retentativa. Guarde e ignore repetido: e o que garante que voce nao credita duas vezes.",
            "examples": [
              "evt_9c3f21a70b5e4d8812ff0a63"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "description": "Mesmo formato do endpoint de status da operacao correspondente."
          }
        },
        "required": [
          "event",
          "created_at",
          "data"
        ]
      },
      "EntregaWebhook": {
        "type": "object",
        "properties": {
          "event_id": {
            "type": "string"
          },
          "event": {
            "type": "string",
            "enum": [
              "cashin.paid",
              "cashin.delayed",
              "cashin.settled",
              "cashin.settlement_failed",
              "cashin.expired",
              "cashin.refunded",
              "cashin.failed",
              "cashout.awaiting_deposit",
              "cashout.deposit_detected",
              "cashout.processing",
              "cashout.under_review",
              "cashout.refunding",
              "cashout.completed",
              "cashout.refunded",
              "cashout.expired",
              "cashout.failed",
              "payout.sent",
              "payout.failed",
              "payout.refunded",
              "webhook.test"
            ]
          },
          "operation_id": {
            "type": "string",
            "description": "Id da operacao que gerou o evento."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "failed"
            ],
            "description": "failed = esgotou as 12 tentativas. Use o retry para recuperar."
          },
          "attempts": {
            "type": "integer"
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "next_attempt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TitularPix": {
        "type": "object",
        "required": [
          "pix_key",
          "pix_key_type",
          "owner_name",
          "verified",
          "source",
          "checked_at"
        ],
        "properties": {
          "pix_key": {
            "type": "string",
            "description": "A chave, normalizada (telefone em +55…)."
          },
          "pix_key_type": {
            "type": "string",
            "enum": [
              "cpf",
              "cnpj",
              "phone",
              "email",
              "random"
            ]
          },
          "owner_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do titular, como está no DICT.",
            "examples": [
              "João Silva"
            ]
          },
          "owner_tax_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "CPF/CNPJ MASCARADO. Nunca vem inteiro.",
            "examples": [
              "***.202.745-**"
            ]
          },
          "institution_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Instituição onde a chave está registrada.",
            "examples": [
              "NU PAGAMENTOS - IP"
            ]
          },
          "ispb": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISPB da instituição (8 dígitos).",
            "examples": [
              "18236120"
            ]
          },
          "verified": {
            "type": "boolean",
            "description": "true = veio do DICT (ou do sandbox, com `sandbox: true`)."
          },
          "source": {
            "type": "string",
            "enum": [
              "dict",
              "sandbox"
            ]
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "cached": {
            "type": "boolean",
            "description": "true quando a resposta veio do cache de 24h."
          },
          "sandbox": {
            "type": "boolean",
            "description": "Só em chave de teste: titular fictício."
          }
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "schema": {
          "type": "integer"
        },
        "description": "Teto de requisicoes por minuto desta chave (= rate_limit_per_minute)."
      },
      "X-RateLimit-Remaining": {
        "schema": {
          "type": "integer"
        },
        "description": "Quantas requisicoes ainda cabem na janela atual."
      },
      "X-RateLimit-Reset": {
        "schema": {
          "type": "integer"
        },
        "description": "Segundos ate o balde reabastecer por completo."
      },
      "Retry-After": {
        "schema": {
          "type": "integer"
        },
        "description": "So no 429: segundos a esperar antes de repetir."
      }
    }
  },
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/v1/verificar/{e2e}": {
      "get": {
        "tags": [
          "Verificação"
        ],
        "operationId": "verificarPagamento",
        "security": [],
        "summary": "Confirmar que um PIX aconteceu (aberto, sem chave)",
        "description": "Serve para checar um pagamento sem depender da palavra de quem diz ter pago. Não exige conta nem chave.\n\nNão devolve chave PIX, nome completo nem documento: prova o pagamento sem expor as partes.\n\nQuando não encontra, a resposta diz explicitamente que o PIX pode ter sido feito por outra instituição — ausência aqui não é prova de que não existiu.",
        "parameters": [
          {
            "name": "e2e",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Identificador do Banco Central: E + 32 caracteres.",
            "example": "E37293930202607312006361894c4a82"
          }
        ],
        "responses": {
          "200": {
            "description": "Pagamento liquidado pela Lunium.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verificado": {
                      "type": "boolean"
                    },
                    "pago": {
                      "type": "boolean"
                    },
                    "e2e": {
                      "type": "string"
                    },
                    "valor_brl": {
                      "type": "string"
                    },
                    "pago_em": {
                      "type": "string",
                      "format": "date-time",
                      "example": "2026-08-01T22:35:49-03:00",
                      "description": "ISO 8601 COM offset (horario de Brasilia). Nunca suponha UTC nem hora local: o offset vem explicito. / (en) ISO 8601 with explicit offset (Brasilia time)."
                    },
                    "recebedor_iniciais": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "instituicao": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "liquidado_por": {
                      "type": "string"
                    },
                    "comprovante_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Não liquidado pela Lunium (pode ter sido por outra instituição)."
          },
          "400": {
            "description": "Formato inválido."
          }
        }
      }
    },
    "/catalog": {
      "get": {
        "tags": [
          "Catálogo"
        ],
        "operationId": "obterCatalogo",
        "summary": "Ativos, redes e limites que liquidam agora",
        "description": "Fonte da verdade. Muda sozinho conforme redes entram e saem — leia daqui em vez de manter lista no código.\n\n`fast` = liquidação em segundos. `convert` = via exchange, prazo da rede. `dex` = tokens long-tail (mande também `token_address` na cotação).",
        "responses": {
          "200": {
            "description": "Catálogo vivo."
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/cash-outs": {
      "post": {
        "tags": [
          "Cash-out"
        ],
        "operationId": "criarCotacao",
        "summary": "Cotar (passo 1 de 3)",
        "description": "Cria a cotação. Ainda não move nada e não gera endereço — o endereço vem no `accept`.\n\nSe a rede não liquidar naquele momento, a resposta é `400` com o motivo. Recusar aqui é barato; aceitar e não pagar é o que custa caro.\n\nLimite por operacao: R$ 6,00 a R$ 250.000,00. O limite e em REAIS e a chamada e em CRIPTO, entao a recusa devolve limits.min_amount e limits.max_amount ja convertidos pela cotacao desta ordem.\n\nA venda NAO tem teto diario: o teto diario por chave (tier) vale para cash-in e payouts. Veja limits.daily_limit_applies_to em GET /keys/me.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "asset",
                  "network",
                  "pix_key"
                ],
                "properties": {
                  "asset": {
                    "type": "string",
                    "examples": [
                      "USDT"
                    ]
                  },
                  "network": {
                    "type": "string",
                    "description": "Veja `GET /catalog`. `polygon` liquida em segundos.",
                    "examples": [
                      "polygon"
                    ]
                  },
                  "amount": {
                    "type": "string",
                    "description": "Quantidade de cripto em string decimal (`\"50\"`), nunca float. Informe `amount` OU `brl_amount` — exatamente um.",
                    "examples": [
                      "50"
                    ]
                  },
                  "brl_amount": {
                    "type": "string",
                    "description": "Cotação REVERSA: o valor do PIX em reais, em string decimal com até 2 casas (`\"250.00\"`). A Lunium calcula a cripto — o `amount` da resposta é o que o cliente deposita. Informe `amount` OU `brl_amount` — exatamente um. (en) Reverse quote: the BRL the PIX key must receive; the response carries the exact crypto `amount` to deposit.",
                    "examples": [
                      "250.00"
                    ]
                  },
                  "pix_key": {
                    "type": "string",
                    "description": "Chave que vai receber os reais. Formatos aceitos (normalizados pela API para o formato exato do liquidante; fora disso → 400 pix_key_invalida antes de existir depósito): CPF 11 dígitos · CNPJ 14 dígitos · telefone internacional +55DDDNÚMERO (+5548996005588) · e-mail · chave aleatória em UUID (6602ede6-b1a9-4e63-9178-c6883fd0095e).\n\n(en) Key that receives the reais. Accepted formats: CPF 11 digits · CNPJ 14 digits · phone +55 + area code + number · e-mail · random key as UUID."
                  },
                  "pix_key_type": {
                    "type": "string",
                    "enum": [
                      "cpf",
                      "cnpj",
                      "phone",
                      "email",
                      "random"
                    ],
                    "description": "Opcional: deduzido do formato da chave (e-mail, CNPJ, UUID, telefone com +55). So e obrigatorio quando a chave tem 11 digitos puros, onde CPF e telefone sao indistinguiveis — nesse caso a API recusa com erro tipo_ambiguo em vez de chutar."
                  },
                  "token_address": {
                    "type": "string",
                    "description": "Endereço/mint exato. Necessário para tokens do `/catalog/dex`, onde símbolos se repetem."
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Seu identificador. Torna a chamada idempotente: repetir devolve a mesma ordem."
                  },
                  "refund_address": {
                    "type": "string",
                    "description": "Carteira de RETORNO do seu cliente, na mesma rede da venda. É para onde a cripto volta em qualquer devolução (provedor recusou a chave PIX, reversão do PIX, falha antes do envio). Informe SEMPRE: sem ela a devolução vai para a origem on-chain do depósito — que é a hot wallet da exchange quando o cliente sacou de uma.\n\n(en) Your customer's RETURN wallet on the same network. Where the crypto goes back on any refund. Always send it: without it the refund goes to the on-chain origin of the deposit — the exchange's wallet when the customer withdrew from one.",
                    "examples": [
                      "0x51e3d44172868acc60d68ca99591ce4230bc75e0"
                    ]
                  },
                  "br_code": {
                    "type": "string",
                    "description": "PIX copia e cola (BR Code) de uma COBRANÇA com valor — para pagar um QR code em vez de uma chave. Só com USDT ou USDC na Polygon (fora disso → 400 br_code_nao_suportado). O valor do QR vira o brl_amount da ordem e a chave do recebedor sai do próprio QR: não envie amount, brl_amount nem pix_key junto. O liquidante valida o QR; se recusar, a cripto volta para refund_address (REFUNDED).\n\n(en) PIX copy-paste string (BR Code) of a charge with a fixed amount — pay a QR code instead of a key. USDT or USDC on Polygon only. Do not send amount, brl_amount or pix_key with it.",
                    "examples": [
                      "00020126330014br.gov.bcb.pix0111498113948905204000053039865406…6304ABCD"
                    ]
                  }
                }
              },
              "examples": {
                "usdt": {
                  "summary": "USDT na Polygon",
                  "value": {
                    "asset": "USDT",
                    "network": "polygon",
                    "amount": "50",
                    "pix_key": "12345678901",
                    "pix_key_type": "cpf",
                    "refund_address": "0x51e3d44172868acc60d68ca99591ce4230bc75e0",
                    "external_id": "saque-4471"
                  }
                },
                "reverso": {
                  "summary": "A partir do valor em reais (cotação reversa)",
                  "value": {
                    "asset": "USDT",
                    "network": "polygon",
                    "brl_amount": "250.00",
                    "pix_key": "vendas@loja.com.br",
                    "external_id": "pedido-9921"
                  }
                },
                "cobranca": {
                  "summary": "Pagar um QR code / copia e cola (só USDT/USDC na Polygon)",
                  "value": {
                    "asset": "USDC",
                    "network": "polygon",
                    "br_code": "00020126330014br.gov.bcb.pix0111498113948905204000053039865406…6304ABCD",
                    "refund_address": "0x51e3d44172868acc60d68ca99591ce4230bc75e0",
                    "external_id": "pedido-9922"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Cotacao criada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashOut"
                }
              }
            }
          },
          "200": {
            "description": "Cotação criada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashOut"
                }
              }
            }
          },
          "400": {
            "description": "Pedido inválido ou rede indisponível.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "`external_id` já usado com outros parâmetros."
          },
          "422": {
            "description": "Parametros semanticamente invalidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      },
      "get": {
        "tags": [
          "Cash-out"
        ],
        "operationId": "listarCashOuts",
        "summary": "Listar ordens",
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Recuperacao idempotente: devolve a operacao com este external_id (0 ou 1). Unico por chave e produto, case-sensitive, retido permanentemente."
          }
        ],
        "responses": {
          "200": {
            "description": "Ordens da sua chave, mais recentes primeiro."
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/cash-outs/{cashout_id}/accept": {
      "post": {
        "tags": [
          "Cash-out"
        ],
        "operationId": "aceitarCotacao",
        "summary": "Aceitar e receber o endereço (passo 2 de 3)",
        "description": "Trava a cotação e devolve `deposit_address`. Envie **exatamente** a quantidade cotada.\n\nSe a rede exigir memo/tag, ele vem em `deposit_tag` — depósito sem o memo se perde.",
        "parameters": [
          {
            "name": "cashout_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Endereço de depósito.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashOut"
                }
              }
            }
          },
          "404": {
            "description": "Ordem não encontrada."
          },
          "422": {
            "description": "Cotação expirada ou já aceita."
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/cash-outs/{cashout_id}": {
      "get": {
        "tags": [
          "Cash-out"
        ],
        "operationId": "consultarCashOut",
        "summary": "Acompanhar (passo 3 de 3)",
        "description": "Prefira o webhook. Se consultar, use uma vez a cada 2 ou 3 segundos: uma vez por segundo consome sozinho o limite de 60 requisições por minuto da chave.\n\nQuando `state` for `COMPLETED`, os campos `pix_e2e`, `pix_paid_at` e `receipt_url` estarão preenchidos.",
        "parameters": [
          {
            "name": "cashout_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Estado atual.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashOut"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/cashin/charge": {
      "post": {
        "tags": [
          "Cash-in"
        ],
        "operationId": "criarCobranca",
        "summary": "Gerar cobrança PIX para entregar cripto",
        "description": "O CPF ou CNPJ do pagador é obrigatório (exigência do Banco Central) e identifica a cobrança. Cada pagador tem uma régua própria de limite, que cresce com o histórico.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount_cents",
                  "payout_address",
                  "payer_tax_number"
                ],
                "properties": {
                  "amount_cents": {
                    "type": "integer",
                    "description": "Valor em centavos. `25000` = R$ 250,00.",
                    "examples": [
                      25000
                    ]
                  },
                  "chain": {
                    "type": "string",
                    "default": "polygon"
                  },
                  "asset": {
                    "type": "string",
                    "enum": [
                      "usdt",
                      "usdc"
                    ],
                    "default": "usdt"
                  },
                  "payout_address": {
                    "type": "string",
                    "description": "Carteira que recebe a cripto."
                  },
                  "payer_tax_number": {
                    "type": "string",
                    "description": "CPF ou CNPJ de quem vai pagar o PIX."
                  },
                  "external_id": {
                    "type": "string"
                  },
                  "destino": {
                    "type": "string",
                    "enum": [
                      "cripto",
                      "saldo"
                    ],
                    "default": "cripto",
                    "description": "Where the paid PIX goes. `cripto` (default): the settlement sends USDT/USDC to `payout_address`. `saldo`: nothing leaves on-chain — the amount (net of `deposito_fee_bps`, 0 today = credited in full) is credited to the key's BRL balance and released after the key's hold (`carencia_horas`, D+1 by default). Requires custody enabled on the key (`custodia_ativa` in GET /saldo)."
                  },
                  "customer_ref": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Optional sub-account of the balance (your end customer id). With `destino: \"saldo\"`, the credit lands on this sub-account; GET /saldo?customer_ref= reads it and POST /payouts with the same `customer_ref` spends it. Omit to use the key's main balance."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "QR e copia-e-cola."
          },
          "403": {
            "description": "Limite do pagador excedido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "400": {
            "description": "Parametros invalidos, valor fora dos limites, ou cash-in indisponivel para esta chave (ex.: chave de sandbox).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/keys": {
      "post": {
        "tags": [
          "Conta"
        ],
        "operationId": "criarChave",
        "security": [],
        "summary": "Criar chave (self-service)",
        "description": "A chave aparece **uma única vez** — guarde no momento da criação, só o hash fica armazenado. A resposta traz também os grupos de acompanhamento, onde mudanças de contrato são anunciadas antes de irem ao ar.",
        "responses": {
          "201": {
            "description": "Chave criada."
          },
          "400": {
            "description": "Campos invalidos (name obrigatorio, webhook_url precisa ser https publica).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de criacao de chaves por IP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/keys/me": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "consultarConta",
        "summary": "Limites, uso e tier atual",
        "responses": {
          "200": {
            "description": "Estado da sua chave."
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Atualiza a chave (nome, settlement, webhook, rotações)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 80
                  },
                  "settlement_address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Endereço Polygon 0x…; null limpa."
                  },
                  "webhook_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "https pública; null limpa. Definir gera webhook_secret se a chave ainda não tem."
                  },
                  "rotate_webhook_secret": {
                    "type": "boolean",
                    "description": "true gira o segredo do webhook — aparece UMA vez na resposta."
                  },
                  "rotate_monitor_token": {
                    "type": "boolean",
                    "description": "true gira o token do monitor: a URL antiga para de mostrar dados na hora; a nova vem em monitor_url."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chave atualizada. Pode trazer webhook_secret (uma única vez) e monitor_url novo."
          },
          "400": {
            "description": "Campos inválidos ou nada para atualizar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/ping": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "ping",
        "security": [],
        "summary": "Saúde da API",
        "responses": {
          "200": {
            "description": "pong"
          }
        }
      }
    },
    "/keys/sandbox": {
      "post": {
        "summary": "Get a sandbox API key — one call, no signup",
        "description": "Returns a `lun_test_…` key that runs the ENTIRE flow without a cent moving, on this same base URL and the same MCP endpoint. No name, e-mail, wallet or approval required.\n\nCode written against the sandbox works in production unchanged: same shapes, same states, same error contract, same validations and the same limits. Orders do NOT complete instantly — they walk the real states over about 15 seconds, so you write the polling loop (or the webhook) you will need anyway.\n\n**Deterministic triggers.** Testing only the happy path is how integrations break on day one, so the first two decimals of `amount` choose the outcome — no randomness, so you can assert on it in CI. `.01` → the order goes to *delayed* (PIX paid, provider holding) and completes on its own; `.02` → it fails; `.03` → the quote expires in 5s; `.04` → refused on limits with `limits` filled; `.05` → completes in about 2 minutes.\n\nReusing an `external_id` with a different destination returns `409`, exactly as production does — the case that confuses every integrator, rehearsable here.\n\nA sandbox order produces a well-formed E2E whose institution code is `00000000`, which no real institution holds, so it can never be mistaken for a real payment. `GET /v1/verificar/{e2e}` answers for it, marked `sandbox: true`.\n\nTest orders disappear 2 hours after creation. Never send crypto to a sandbox `deposit_address`: it has no owner and the funds would be lost.\n\nSem autenticacao, de proposito: e a porta de entrada. Um agente de IA que recebeu erro=chave_ausente pode chamar isto e seguir sozinho, em vez de parar para pedir credenciais ao usuario. A chave lun_test_ roda o fluxo inteiro e nada liquida. / (en) Unauthenticated on purpose: an AI agent that got erro=chave_ausente can provision its own test key here instead of stopping.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Optional label to recognise the key later."
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Optional HTTPS webhook."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sandbox key created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "api_key": {
                      "type": "string",
                      "example": "lun_test_1df82296e3459819702c6a6087aab1c7"
                    },
                    "sandbox": {
                      "type": "boolean",
                      "const": true
                    },
                    "gatilhos": {
                      "type": "object",
                      "description": "The deterministic triggers, described above."
                    },
                    "diferencas_para_producao": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many keys from this IP today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/cashin/limits": {
      "get": {
        "summary": "How much this taxpayer can pay right now",
        "description": "Returns how much a specific Brazilian taxpayer (CPF for a person, CNPJ for a company) can move through a PIX charge **right now**, in cents — before you create the charge.\n\nThere is an anti-fraud ladder per taxpayer, independent of your key: a first-time payer starts at R$ 60,00 per operation; after their first CONFIRMED payment the ceiling rises to R$ 200,00 for the first 24 hours; after that it is R$ 6.000,00 per day. The clock starts at the first confirmed payment, not the first charge created — creating a charge is free, paying is not, so nobody can age an account by generating QR codes.\n\n`max_amount_cents` is literally the largest value that passes now. Guessing a value and taking a 403 works for a human who adjusts on the spot; for an agent it becomes a loop.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "payer_tax",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^([0-9]{11}|[0-9]{14})$"
            },
            "description": "CPF (11 digits) or CNPJ (14 digits), digits only."
          }
        ],
        "responses": {
          "200": {
            "description": "Current ladder state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payer_tax": {
                      "type": "string",
                      "description": "Masked back to you, never in full."
                    },
                    "stage": {
                      "enum": [
                        "first_transaction",
                        "first_24h",
                        "mature",
                        "disabled",
                        "desconhecido"
                      ]
                    },
                    "max_amount_cents": {
                      "type": "integer",
                      "description": "The largest value that passes now."
                    },
                    "used_cents": {
                      "type": "integer"
                    },
                    "available_cents": {
                      "type": "integer"
                    },
                    "limit_cents": {
                      "type": "integer"
                    },
                    "next_stage": {
                      "type": "object",
                      "nullable": true,
                      "description": "When the ceiling rises and to what."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/cashin/{cashin_id}/status": {
      "get": {
        "summary": "State of a PIX charge",
        "description": "States: `pending` (unpaid) · `under_review` (PIX received, settlement in transit) · `paid` (credited, crypto released) · `delayed` · `expired` · `refunded` · `failed`.\n\n**`delayed` is the state that costs money when misread:** the PIX **was paid** and the provider is holding the release, commonly on a payer's first operation. The response carries `delay_until`, and it becomes `paid` on its own. Do not tell the user the payment failed, do not create a second charge, and do not ask them to pay again.\n\nPoll at most every 10–15 seconds: once per second consumes the 60-per-minute key budget by itself. Prefer the webhook.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "cashin_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Charge state"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/payouts": {
      "post": {
        "summary": "Send a PIX directly (no crypto leg)",
        "description": "Sends Brazilian reais straight to a PIX key. Unlike cash-out, there is no incoming crypto leg — the money leaves Lunium's treasury.\n\nBecause of that it is not open on day one: it **unlocks by itself**, with no e-mail and no waiting, once the key has settled R$ 1.000,00 in cash-in or cash-out. Until then the 403 tells you exactly how much is missing. Nobody has to approve anything.\n\nSend `external_id` so a retry does not send a second PIX for the same intent.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Value in CENTS. Integer only."
                  },
                  "pix_key": {
                    "type": "string"
                  },
                  "pix_key_type": {
                    "enum": [
                      "cpf",
                      "cnpj",
                      "phone",
                      "email",
                      "random"
                    ],
                    "description": "Opcional: deduzido do formato da chave (e-mail, CNPJ, UUID, telefone com +55). So e obrigatorio quando a chave tem 11 digitos puros, onde CPF e telefone sao indistinguiveis — nesse caso a API recusa com erro tipo_ambiguo em vez de chutar."
                  },
                  "tax_number": {
                    "type": "string",
                    "description": "CPF or CNPJ of the beneficiary, digits only."
                  },
                  "external_id": {
                    "type": "string"
                  },
                  "customer_ref": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Sub-account to debit (custody keys). Debited BEFORE the PIX is sent: amount + house fee (`saque_fee_bps`) + provider fee; on explicit provider refusal the debits are reversed. 402 `saldo_insuficiente` when available balance < amount + fees (`saque_fee_bps`, `saque_taxa_estimada_cents` in GET /saldo)."
                  }
                },
                "required": [
                  "amount_cents",
                  "pix_key"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payout created"
          },
          "403": {
            "description": "Not unlocked yet — the body says how much is missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "external_id already used with different parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "Lista os payouts desta chave",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "processing",
                "sent",
                "failed",
                "refunded"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Recuperacao idempotente: devolve a operacao com este external_id (0 ou 1). Unico por chave e produto, case-sensitive, retido permanentemente."
          }
        ],
        "responses": {
          "200": {
            "description": "Payouts, mais recentes primeiro.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payouts": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Payout ainda nao liberado para esta chave.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/payouts/{payout_id}": {
      "get": {
        "summary": "State of a payout",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "payout_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payout state"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/deliveries": {
      "get": {
        "summary": "Historico de entregas de webhook desta chave",
        "description": "Diagnostico do que saiu, o que falhou e por que. Nao devolve o corpo do evento — para reenviar o corpo original use o retry.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "sent",
                "failed"
              ]
            }
          },
          {
            "name": "event",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "cashin.paid",
                "cashin.delayed",
                "cashin.settled",
                "cashin.settlement_failed",
                "cashin.expired",
                "cashin.refunded",
                "cashin.failed",
                "cashout.awaiting_deposit",
                "cashout.deposit_detected",
                "cashout.processing",
                "cashout.under_review",
                "cashout.refunding",
                "cashout.completed",
                "cashout.refunded",
                "cashout.expired",
                "cashout.failed",
                "payout.sent",
                "payout.failed",
                "payout.refunded",
                "webhook.test"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entregas, mais recentes primeiro.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deliveries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EntregaWebhook"
                      }
                    },
                    "failed_count": {
                      "type": "integer",
                      "description": "Quantas na resposta esgotaram as tentativas."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Filtro invalido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/deliveries/{event_id}/retry": {
      "post": {
        "summary": "Reenviar um evento",
        "description": "Tenta entregar na hora e responde com o resultado. Se falhar de novo, a entrega volta para a fila com 12 tentativas novas. Recusa reenviar o que ja foi confirmado com 2xx.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "event_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entregue agora.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "event_id": {
                      "type": "string"
                    },
                    "delivered": {
                      "type": "boolean",
                      "const": true
                    },
                    "status_code": {
                      "type": "integer"
                    },
                    "ms": {
                      "type": "integer"
                    },
                    "delivery_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Nao entregue agora; recolocada na fila."
          },
          "404": {
            "description": "Nao encontrado para esta chave",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "409": {
            "description": "Ja entregue — reenviar duplicaria",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/test": {
      "post": {
        "summary": "Disparar um evento de teste agora",
        "description": "Envia um `webhook.test` para a sua webhook_url pelo MESMO transporte da producao e devolve o que o seu endpoint respondeu (status e latencia). Nenhum dinheiro se move e nada entra na fila. Use antes de ligar a primeira cobranca real. Maximo de 6 por minuto.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Seu endpoint respondeu 2xx.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "event_id": {
                      "type": "string"
                    },
                    "delivery_id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "status_code": {
                      "type": "integer"
                    },
                    "ms": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "webhook_url nao configurada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Mais de 6 testes por minuto",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "502": {
            "description": "Seu endpoint nao respondeu 2xx — o corpo diz exatamente o que aconteceu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/cashin/charges": {
      "get": {
        "summary": "Lista/recupera cobrancas de cash-in desta chave",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "external_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Recuperacao idempotente: devolve a operacao com este external_id (0 ou 1). Unico por chave e produto, case-sensitive, retido permanentemente."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "end",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cobrancas, mais recentes primeiro (ou a unica com o external_id).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "charges": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente/invalida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "Listagem exige X-API-Key (nao publico).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/cashin/preview": {
      "post": {
        "summary": "Cotação de cash-in — não cria nada",
        "description": "Quanto da stablecoin sai por um valor em BRL, já com a taxa da sua chave aplicada. Read-only: nenhum QR é emitido. Os campos retornados são a fonte de verdade — não recalcule localmente.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount_cents"
                ],
                "properties": {
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Valor do PIX em centavos."
                  },
                  "asset": {
                    "type": "string",
                    "enum": [
                      "usdt",
                      "usdc",
                      "depix"
                    ],
                    "default": "usdt"
                  },
                  "chain": {
                    "type": "string",
                    "default": "polygon"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cotação com a taxa da chave aplicada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "asset": {
                      "type": "string"
                    },
                    "amount_cents": {
                      "type": "integer"
                    },
                    "admin_fee_bps": {
                      "type": "integer"
                    },
                    "admin_fee_fixed_cents": {
                      "type": "integer"
                    },
                    "fee_total_cents": {
                      "type": "integer"
                    },
                    "net_cents": {
                      "type": "integer"
                    },
                    "brl_per_usdt": {
                      "type": "string",
                      "description": "Cotação arredondada p/ leitura — o valor entregue usa a cotação cheia."
                    },
                    "usdt_amount": {
                      "type": "string",
                      "description": "Quantidade líquida entregue. Fonte de verdade."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Valor fora dos limites da chave ou parâmetros inválidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/pix/keys/lookup": {
      "get": {
        "operationId": "consultarTitularChavePix",
        "summary": "Who owns this Pix key (DICT) — verify the recipient BEFORE paying",
        "description": "Consulta o titular de uma chave PIX no DICT: nome, CPF/CNPJ **mascarado** e instituição. É o que permite mostrar \"você vai pagar Fulano · Nubank\" antes de aceitar uma venda. O tipo é deduzido do formato (11 dígitos: CPF se o dígito verificador fecha, senão telefone); `type` explícito vence. Cache de 24h por chave. **Limite: 5 consultas por minuto na conta inteira** (todos os clientes) — acima disso 429 `limite_consultas` com `Retry-After`. Chave inexistente devolve 404 `chave_nao_encontrada` (e fica em cache por 1h). Chave de sandbox recebe um titular de teste, na mesma forma, sem consultar o DICT. (en) Looks up the holder of a Pix key in the Central Bank directory: name, masked tax number, institution.",
        "tags": [
          "Pix"
        ],
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A chave PIX (CPF, CNPJ, e-mail, telefone com ou sem +55, ou aleatória).",
            "examples": {
              "email": {
                "value": "vendas@loja.com.br"
              },
              "telefone": {
                "value": "+5511987654321"
              }
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "cpf",
                "cnpj",
                "phone",
                "email",
                "random"
              ]
            },
            "description": "Opcional. Deduzido do formato quando ausente."
          }
        ],
        "responses": {
          "200": {
            "description": "Titular encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TitularPix"
                }
              }
            }
          },
          "400": {
            "description": "Chave em formato inválido (`pix_key_invalida`) ou ausente (`pix_key_obrigatoria`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "401": {
            "description": "X-API-Key ausente, desconhecida ou incorreta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "404": {
            "description": "Chave não existe no DICT (`chave_nao_encontrada`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de 5 consultas/min da conta (`limite_consultas`); leia `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "503": {
            "description": "Provedor DICT indisponível (`provedor_indisponivel`, repetir) ou consulta desligada (`consulta_indisponivel`, esperar).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/saldo": {
      "get": {
        "tags": [
          "Conta"
        ],
        "summary": "BRL balance (custody) — available, on hold, next releases",
        "description": "Balance of the key (or of one sub-account with `customer_ref`). Ledger is append-only: balance = sum of movements. Deposits with `destino: \"saldo\"` enter as *bloqueado* and move to *disponivel* at `disponivel_em` (hold = `carencia_horas`). Also returns the fees the app must show: `deposito_fee_bps` (deducted on credit) and `saque_taxa_estimada_cents` (provider fee per payout, debited from the balance).",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "customer_ref",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Sub-account (your end customer id). Omit for the key's main balance."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "disponivel_cents",
                    "bloqueado_cents",
                    "total_cents",
                    "custodia_ativa"
                  ],
                  "properties": {
                    "customer_ref": {
                      "type": "string",
                      "nullable": true
                    },
                    "disponivel_cents": {
                      "type": "integer"
                    },
                    "bloqueado_cents": {
                      "type": "integer"
                    },
                    "total_cents": {
                      "type": "integer"
                    },
                    "movimentos": {
                      "type": "integer"
                    },
                    "proximas_liberacoes": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "disponivel_em": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "valor_cents": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "custodia_ativa": {
                      "type": "boolean",
                      "description": "false = this key cannot hold BRL (destino \"saldo\" is refused)."
                    },
                    "carencia_horas": {
                      "type": "integer"
                    },
                    "deposito_fee_bps": {
                      "type": "integer",
                      "description": "Fee deducted on deposit credit, in bps. 0 today: deposits are credited in full."
                    },
                    "saque_taxa_estimada_cents": {
                      "type": "integer",
                      "description": "Provider (bank) fee per PIX payout, in cents, debited from the balance in addition to `amount_cents` and `saque_fee_bps`."
                    },
                    "saque_fee_bps": {
                      "type": "integer",
                      "description": "House fee on withdrawals, in bps of the amount (deposits are credited in full; the cost is recovered here)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid key"
          }
        }
      }
    },
    "/saldo/extrato": {
      "get": {
        "tags": [
          "Conta"
        ],
        "summary": "Balance statement (movements, newest first)",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "customer_ref",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "movimentos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "valor_cents": {
                            "type": "integer"
                          },
                          "sentido": {
                            "type": "string",
                            "enum": [
                              "credito",
                              "debito"
                            ]
                          },
                          "tipo": {
                            "type": "string",
                            "enum": [
                              "cashin",
                              "payout",
                              "cashout",
                              "estorno",
                              "ajuste"
                            ]
                          },
                          "origem_tipo": {
                            "type": "string",
                            "nullable": true
                          },
                          "origem_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "disponivel_em": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "disponivel": {
                            "type": "boolean"
                          },
                          "descricao": {
                            "type": "string",
                            "nullable": true
                          },
                          "criado_em": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "proxima_pagina": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid key"
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Guia de integracao, regras que evitam erro caro e as 9 ferramentas do MCP",
    "url": "https://docs.luniumpay.com/#armadilhas"
  },
  "webhooks": {
    "evento": {
      "post": {
        "summary": "Evento enviado por nos ao seu endpoint",
        "description": "Configure `webhook_url` (https publica) em POST /keys ou PATCH /keys/me. Responda 2xx rapido e processe async: qualquer coisa fora de 2xx vira retry com backoff exponencial (12 tentativas, ~7h). Verifique SEMPRE a assinatura. Entregas que esgotam as tentativas ficam visiveis em GET /webhooks/deliveries e podem ser recuperadas com o retry.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventoWebhook"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            },
            "description": "DEPRECATED, desligamento em 2026-11-09 — chaves criadas a partir de 2026-08-08 nao a recebem. Verifique a v2 (X-Lunium-Signature, HMAC de \"timestamp.corpo\", recuse > 5 min). Ate a data, integracoes existentes continuam recebendo esta assinatura v1 (HMAC do corpo bruto) normalmente.",
            "deprecated": true
          },
          {
            "name": "X-Lunium-Signature",
            "in": "header",
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "Assinatura v2: `t=<unix>,v1=<HMAC-SHA256 de \"<t>.<corpo bruto>\">`. Prefira esta: como o timestamp entra no HMAC, voce pode recusar entregas antigas e uma entrega capturada nao pode ser reenviada contra voce depois. Tolerancia recomendada: 5 minutos."
          },
          {
            "name": "X-Lunium-Event",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Nome do evento, para rotear sem abrir o corpo."
          },
          {
            "name": "X-Lunium-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Igual ao event_id do corpo."
          },
          {
            "name": "X-Lunium-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Identifica esta TENTATIVA (muda a cada retry). Cite no suporte."
          },
          {
            "name": "X-Lunium-Attempt",
            "in": "header",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Numero da tentativa. >1 significa que voce ja pode ter recebido este evento."
          },
          {
            "name": "X-Lunium-Timestamp",
            "in": "header",
            "schema": {
              "type": "integer"
            },
            "description": "Unix time do envio; o mesmo `t` da v2."
          }
        ],
        "responses": {
          "200": {
            "description": "Recebido. Qualquer 2xx encerra a entrega."
          },
          "default": {
            "description": "Nao-2xx agenda nova tentativa."
          }
        }
      }
    }
  }
}