{
  "openapi": "3.1.0",
  "info": {
    "title": "Lunium — crypto ⇄ PIX settlement (Brazil)",
    "version": "1.0.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\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.",
    "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."
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "description": "Toda falha segue este formato. `code` é estável e legível por máquina; `acao` diz o que fazer, para quem está automatizando não precisar interpretar prosa.",
        "properties": {
          "detail": {
            "type": "string",
            "description": "Explicação em português, para humano."
          },
          "code": {
            "type": "string",
            "description": "Código estável do erro.",
            "examples": [
              "rede_indisponivel",
              "chave_invalida",
              "limite_excedido",
              "valor_invalido"
            ]
          },
          "acao": {
            "type": "string",
            "enum": [
              "corrigir",
              "repetir",
              "esperar",
              "parar"
            ],
            "description": "What to do next — matters more than the message. corrigir: the request is wrong, repeating it identically never works. repetir: transient failure on our side, try again. esperar: a quota renews, come back later. parar: do not retry, talk to us.\n\n(pt) O que fazer a seguir -- vale mais que a mensagem. corrigir: o pedido esta errado, repetir igual nunca passa. repetir: falha transitoria nossa, tente de novo. esperar: a cota renova, volte depois. parar: nao insista, fale com a gente. Existe porque 'ordem maior que a cota de um dia inteiro' e 'cota de hoje quase cheia' ja devolveram a mesma resposta ('tente amanha') -- e no primeiro caso amanha falha igual, para sempre.",
            "example": "corrigir"
          },
          "erro": {
            "type": "string",
            "description": "Codigo estavel de maquina. Nao muda quando a mensagem e reescrita.",
            "example": "valor_abaixo_do_minimo"
          },
          "limits": {
            "type": "object",
            "nullable": true,
            "description": "Presente nos erros de limite. Nas recusas por valor traz min_amount e max_amount JA CONVERTIDOS para a cripto da ordem pela cotacao daquela chamada: e literalmente o proximo valor a mandar, sem o integrador ter que converter reais para cripto por conta propria.",
            "example": {
              "min_brl_cents": 500,
              "max_brl_cents": 5000000,
              "quoted_brl_cents": 229,
              "min_amount": "1.091704",
              "max_amount": "10917.030567",
              "asset": "USDT",
              "network": "polygon"
            }
          }
        }
      },
      "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"
            ]
          },
          "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",
            "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.",
            "example": "https://app.luniumpay.com/comprovante/qigpnCTx3gM-Ge7hN27Mvw.pdf"
          },
          "verify_url": {
            "type": "string",
            "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.",
            "example": "https://api.luniumpay.com/v1/verificar/E3729393020260731214021222ac6ff1"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  },
  "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"
                    },
                    "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."
          }
        }
      }
    },
    "/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$ 50.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\nAlem dele existe um teto DIARIO por chave, que cresce com o volume liquidado e vem em limits quando atingido.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "asset",
                  "network",
                  "amount",
                  "pix_key",
                  "pix_key_type"
                ],
                "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.",
                    "examples": [
                      "50"
                    ]
                  },
                  "pix_key": {
                    "type": "string",
                    "description": "Chave que vai receber os reais."
                  },
                  "pix_key_type": {
                    "type": "string",
                    "enum": [
                      "cpf",
                      "cnpj",
                      "phone",
                      "email",
                      "random"
                    ],
                    "description": "Obrigatório: CPF e telefone têm ambos 11 dígitos e são indistinguíveis sem isto."
                  },
                  "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."
                  }
                }
              },
              "examples": {
                "usdt": {
                  "summary": "USDT na Polygon",
                  "value": {
                    "asset": "USDT",
                    "network": "polygon",
                    "amount": "50",
                    "pix_key": "12345678901",
                    "pix_key_type": "cpf",
                    "external_id": "saque-4471"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "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."
          }
        }
      },
      "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
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ordens da sua chave, mais recentes primeiro."
          }
        }
      }
    },
    "/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."
          }
        }
      }
    },
    "/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"
                }
              }
            }
          }
        }
      }
    },
    "/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"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "QR e copia-e-cola."
          },
          "403": {
            "description": "Limite do pagador excedido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/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."
          }
        }
      }
    },
    "/keys/me": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "consultarConta",
        "summary": "Limites, uso e tier atual",
        "responses": {
          "200": {
            "description": "Estado da sua chave."
          }
        }
      }
    },
    "/ping": {
      "get": {
        "tags": [
          "Conta"
        ],
        "operationId": "ping",
        "security": [],
        "summary": "Saúde da API",
        "responses": {
          "200": {
            "description": "pong"
          }
        }
      }
    }
  }
}