{"openapi":"3.0.3","info":{"title":"Hub de Gateways","version":"1.0.0","description":"API única de pagamentos. A gateway usada por trás (Beehive, Velani, Pingu, Pague-X, FreePay) é escolhida no painel do cliente; o contrato abaixo não muda. Valores sempre em centavos. Chame SEMPRE do servidor: a chave é secreta. Idempotência: mande o header `Idempotency-Key` em cada criação (sem ele, o campo `reference` faz esse papel); repetir a mesma requisição em até 24h devolve a MESMA cobrança (sem criar outra) com o header `x-idempotent-replay: 1`. Webhook: o hub faz POST na URL configurada no painel com {event:'charge.updated', charge:{...}} e o header x-hub-signature = 'sha256=' + HMAC-SHA256 hex do corpo BRUTO com o segredo de assinatura (whs_...). Responda 2xx e processe de forma idempotente. Se o seu servidor não responder 2xx, o hub tenta de novo sozinho (backoff crescente, até ~2h45 por evento). Limite: 120 requisições por minuto por cliente em cada endpoint de escrita (POST /api/v1/charges e POST /api/v1/tracking/sales); ao passar, a resposta é 429 com Retry-After em segundos."},"servers":[{"url":"https://localhost:3003"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Chave do cliente: hub_live_..."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"Address":{"type":"object","required":["zipCode","street","number","city","state"],"properties":{"zipCode":{"type":"string"},"street":{"type":"string"},"number":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"neighborhood":{"type":"string"}}},"ChargeInput":{"type":"object","required":["amount","method","customer"],"properties":{"amount":{"type":"integer","minimum":1,"description":"Centavos. 1990 = R$ 19,90."},"method":{"type":"string","enum":["pix","boleto","card"]},"reference":{"type":"string","description":"Id do pedido no sistema do cliente."},"funnel":{"type":"string","description":"Funil de origem (a-z, 0-9, . _ -; até 60). Também aceito no header X-Hub-Funnel. Se mapeado a uma gateway no painel, a cobrança usa essa gateway; senão, a padrão."},"description":{"type":"string"},"customer":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string"},"email":{"type":"string"},"document":{"type":"string","description":"CPF/CNPJ (só números). Obrigatório em Beehive, Pingu, Pague-X e FreePay."},"phone":{"type":"string","description":"Com DDD. Obrigatório na Pingu e na FreePay."},"address":{"$ref":"#/components/schemas/Address","description":"Obrigatório para boleto na Pague-X."}}},"ip":{"type":"string","description":"IP do COMPRADOR. Obrigatório na Beehive."},"cardToken":{"type":"string","description":"Token/hash gerado no front pelo SDK da gateway (só method=card)."},"installments":{"type":"integer","default":1},"shopUrl":{"type":"string"},"checkoutUrl":{"type":"string"}}},"Charge":{"type":"object","properties":{"id":{"type":"string","description":"Id no hub: <gateway>_<idExterno>. Guarde-o."},"gateway":{"type":"string","enum":["beehive","velani","pingu","paguex","freepay","mock"]},"status":{"type":"string","enum":["pending","paid","failed","canceled","refunded","chargeback"]},"amount":{"type":"integer"},"method":{"type":"string","enum":["pix","boleto","card"]},"reference":{"type":"string"},"pix":{"type":"object","properties":{"copyPaste":{"type":"string","description":"Código 'copia e cola'. Sempre presente em Pix."},"qrCode":{"type":"string","description":"Imagem do QR (URL/base64) quando a gateway fornece; vazio na Beehive e na FreePay."}}},"boleto":{"type":"object","properties":{"url":{"type":"string"},"barcode":{"type":"string"}}},"expiresAt":{"type":"string"},"checkoutUrl":{"type":"string","description":"Se presente, redirecione o comprador."}}},"ExternalSaleInput":{"type":"object","required":["reference","amount","status","method"],"description":"Venda feita em OUTRO checkout (o seu): entra no rastreamento, na atribuição por UTM e no Meta (Purchase). A `reference` deduplica: reenviar devolve a mesma venda; outro `status` atualiza o mesmo registro.","properties":{"reference":{"type":"string","maxLength":150,"description":"Id do pedido no seu sistema. Chave de deduplicação."},"amount":{"type":"integer","minimum":1,"maximum":100000000,"description":"Centavos. 4990 = R$ 49,90."},"status":{"type":"string","enum":["paid","pending","refunded","chargeback"]},"method":{"type":"string","enum":["pix","boleto","card"]},"paidAt":{"type":"string","description":"ISO de quando foi aprovada (padrão: agora, quando status=paid)."},"gateway":{"type":"string","maxLength":40,"description":"Adquirente que processou a venda (texto livre). Padrão: external."},"funnel":{"type":"string","description":"Funil de origem (a-z, 0-9, . _ -; até 60)."},"items":{"type":"array","maxItems":20,"description":"Produtos da venda (principal, order bump, upsell).","items":{"$ref":"#/components/schemas/ExternalSaleItem"}},"product":{"$ref":"#/components/schemas/ExternalSaleItem"},"email":{"type":"string","description":"E-mail do comprador (vai em hash para o Meta; não é devolvido)."},"phone":{"type":"string","description":"Telefone com DDD (vai em hash para o Meta)."},"ip":{"type":"string","description":"IP do comprador (atribuição por IP e Meta)."},"ua":{"type":"string","description":"User agent do comprador (atribuição por IP + navegador)."},"visitorId":{"type":"string","description":"window.HubTrack.visitorId. Caminho mais preciso de atribuição."},"attribution":{"$ref":"#/components/schemas/Attribution","description":"Origem enviada pelo seu sistema (tem prioridade sobre o último toque)."},"utm_source":{"type":"string","description":"Também aceito no topo do corpo (junto com utm_medium, utm_campaign, utm_content, utm_term, fbclid, gclid, ttclid)."}}},"Attribution":{"type":"object","properties":{"utm_source":{"type":"string"},"utm_medium":{"type":"string"},"utm_campaign":{"type":"string"},"utm_content":{"type":"string"},"utm_term":{"type":"string"},"fbclid":{"type":"string"},"gclid":{"type":"string"},"ttclid":{"type":"string"},"fbp":{"type":"string"},"fbc":{"type":"string"},"referrer":{"type":"string"},"landing":{"type":"string"}}},"ExternalSaleItem":{"type":"object","required":["name","price"],"properties":{"id":{"type":"string","description":"Padrão: o próprio name."},"name":{"type":"string"},"price":{"type":"integer","description":"Centavos, unitário."},"quantity":{"type":"integer","minimum":1,"maximum":1000,"default":1},"kind":{"type":"string","enum":["main","bump","upsell"]}}}}},"paths":{"/api/v1/charges":{"post":{"operationId":"createCharge","summary":"Cria uma cobrança na gateway ativa do cliente (idempotente)","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Chave única por requisição de checkout (ex.: id do pedido). Sem ela, o campo `reference` do corpo é usado. Reenvio da MESMA cobrança devolve a mesma resposta (sem criar outra) com o header x-idempotent-replay. Use uma chave diferente para forçar uma nova cobrança (ex.: Pix expirado)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChargeInput"}}}},"responses":{"201":{"description":"Cobrança criada (ou já existente com esta chave)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Charge"}}}},"400":{"description":"Corpo inválido ou campo obrigatório ausente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Chave ausente, inválida ou cliente desativado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Uma cobrança com esta Idempotency-Key está em processamento; repita a mesma requisição em instantes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Método/valor não suportado pela gateway ativa, dado obrigatório ausente ou recusa da gateway","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Limite por cliente excedido (padrão 120 req/min). Espere o `Retry-After` (segundos) e repita.","headers":{"Retry-After":{"description":"Segundos até poder repetir","schema":{"type":"integer"}},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}}},"502":{"description":"Gateway indisponível ou com erro (pode tentar de novo; a chave idempotente fica liberada)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Gateway ativa sem API key configurada no painel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/charges/{id}":{"get":{"operationId":"getCharge","summary":"Consulta uma cobrança (status atual lido na gateway)","description":"Ids `ext_...` são vendas registradas por POST /api/v1/tracking/sales: nesse caso o registro vem do rastreamento do hub (não há gateway para consultar) e a resposta traz `source: \"external\"`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"O id retornado na criação (gateway_id ou ext_hash)."},{"name":"events","in":"query","required":false,"schema":{"type":"string","enum":["1"]},"description":"Passe ?events=1 para receber também `events`: a timeline da cobrança (últimos 100 eventos: criação, webhooks, mudanças de status, Meta, reconciliação; guardados por 90 dias)."}],"responses":{"200":{"description":"Cobrança (com `events` quando ?events=1)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Charge"}}}},"401":{"description":"Chave inválida","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cobrança não encontrada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Gateway indisponível","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tracking/sales":{"post":{"operationId":"recordExternalSale","summary":"Registra uma venda feita em outro checkout (idempotente por reference)","description":"Use quando o pagamento NÃO passa pelo hub, mas você quer a venda no rastreamento (receita, ROAS, lucro, atribuição por UTM) e no Meta. A `reference` é a chave de deduplicação: a mesma reference devolve a MESMA venda (sem duplicar) e um `status` diferente atualiza o mesmo registro — envie `pending` na criação e `paid`/`refunded`/`chargeback` depois. A origem segue a mesma ordem da cobrança do hub: `visitorId`, depois IP + user agent, depois IP. Quando `status=paid`, o hub envia o Purchase à API de Conversões (uma vez por venda).","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalSaleInput"}}}},"responses":{"200":{"description":"Venda já existia: nada mudou (`duplicate: true`) ou o status foi atualizado"},"201":{"description":"Venda registrada (primeira vez com esta reference)"},"401":{"description":"Chave ausente, inválida ou cliente desativado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Corpo inválido: campo faltando, tipo errado ou valor fora do contrato","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Limite por cliente excedido (padrão 120 req/min). Espere o `Retry-After` (segundos) e repita.","headers":{"Retry-After":{"description":"Segundos até poder repetir","schema":{"type":"integer"}},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}}}},"500":{"description":"Falha ao gravar a venda; repita a mesma requisição","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}