{
  "openapi": "3.0.3",
  "info": {
    "title": "LiquiBR API",
    "version": "1.3.0",
    "description": "API REST para gerar cobranças PIX e receber USDT na sua carteira automaticamente. Autenticação via API key (Bearer ou X-API-Key); webhooks assinados via HMAC-SHA256 com proteção anti-replay por timestamp.",
    "contact": {
      "name": "LiquiBR Suporte",
      "url": "https://liquibr.com/support",
      "email": "suporte@liquibr.com"
    }
  },
  "servers": [
    { "url": "https://liquibr.com/v1", "description": "Produção" }
  ],
  "security": [
    { "BearerAuth": [] }
  ],
  "paths": {
    "/account": {
      "get": {
        "summary": "Info da conta",
        "description": "Retorna informações do dono da API key. Útil pra validar a chave durante setup.",
        "tags": ["Conta"],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Account" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/balance": {
      "get": {
        "summary": "Saldo bruto e líquido + status MED",
        "tags": ["Conta"],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Balance" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/charges": {
      "post": {
        "summary": "Cria cobrança PIX",
        "description": "Gera um QR code PIX. Aceita webhook_url inline pra receber callback assinado quando for pago/expirar/sofrer MED.\n\n**Idempotência:** se você informar `external_id` OU o header `Idempotency-Key`, qualquer chamada repetida nas próximas **24 horas** com o mesmo valor retorna a charge existente (HTTP 200 + header `Idempotent-Replay: true`). Mesmo external_id com valor diferente retorna 409 `idempotency_conflict`.",
        "tags": ["Cobranças"],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": { "type": "string", "maxLength": 120 },
            "description": "Alternativa ao `external_id` no body. Mesma janela de 24h. Se ambos forem enviados, `external_id` do body tem prioridade."
          }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateChargeRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Resposta idempotente — charge com mesmo external_id já existia nas últimas 24h. Body é igual ao 201, com `idempotent_replay: true` adicional. Header `Idempotent-Replay: true` também é enviado.",
            "headers": {
              "Idempotent-Replay": { "schema": { "type": "string", "enum": ["true"] } }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Charge" } } }
          },
          "201": {
            "description": "Cobrança criada",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Charge" } } }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "$ref": "#/components/responses/IdempotencyConflict" },
          "429": { "$ref": "#/components/responses/RateLimit" },
          "502": { "$ref": "#/components/responses/PspError" }
        }
      },
      "get": {
        "summary": "Lista cobranças",
        "tags": ["Cobranças"],
        "parameters": [
          { "name": "status",           "in": "query", "schema": { "type": "string", "enum": ["pending", "paid", "expired", "failed", "refunded"] } },
          { "name": "date_from",        "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Filtra charges com `created_at` >= esta data (YYYY-MM-DD). Inclusive." },
          { "name": "date_to",          "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Filtra charges com `created_at` <= fim deste dia (YYYY-MM-DD). Inclusive." },
          { "name": "payer_document",   "in": "query", "schema": { "type": "string" }, "description": "Filtra por CPF/CNPJ do pagador. Aceita formatado (`123.456.789-01`) ou só dígitos." },
          { "name": "customer_email",   "in": "query", "schema": { "type": "string" }, "description": "Filtra charges associadas a clients com este email (match exato, case-insensitive)." },
          { "name": "external_id",      "in": "query", "schema": { "type": "string" }, "description": "Filtra pelo external_id que você passou na criação." },
          { "name": "limit",            "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } },
          { "name": "offset",           "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data":   { "type": "array", "items": { "$ref": "#/components/schemas/ChargeListItem" } },
                    "total":  { "type": "integer" },
                    "limit":  { "type": "integer" },
                    "offset": { "type": "integer" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/charges/{id}": {
      "get": {
        "summary": "Consulta cobrança por id ou external_id",
        "tags": ["Cobranças"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "ID retornado pelo LiquiBR OU external_id que você passou na criação." }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Charge" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "summary": "Cancela cobrança pendente",
        "description": "Cancela explicitamente uma charge que ainda está `pending` antes que ela expire sozinha. Útil quando o pedido interno foi cancelado pelo cliente. Charge passa pra status `refunded`. **Só funciona em `pending`** — se já foi paga, use o endpoint de estorno (em roadmap).",
        "tags": ["Cobranças"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "ID interno OU external_id da charge." }
        ],
        "responses": {
          "200": {
            "description": "Cancelada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id":        { "type": "string" },
                    "status":    { "type": "string", "enum": ["refunded"] },
                    "cancelled_at": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": {
            "description": "Charge não está em status pending (já paga, expirada ou cancelada)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/charges/{id}/refund": {
      "post": {
        "summary": "Estorno de cobrança paga (NÃO IMPLEMENTADO)",
        "description": "Endpoint reservado pra estorno de charges com status `paid`. Atualmente retorna **501 not_implemented** — estornos devem ser feitos via suporte. O endpoint existe pra você não precisar mudar o cliente quando for liberado.",
        "tags": ["Cobranças"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "501": {
            "description": "Estornos via API ainda não estão disponíveis",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error":   { "type": "string", "enum": ["not_implemented"] },
                    "message": { "type": "string" },
                    "support": {
                      "type": "object",
                      "properties": {
                        "email":      { "type": "string" },
                        "ticket_url": { "type": "string" }
                      }
                    },
                    "charge_reference":         { "type": "string" },
                    "estimated_response_time":  { "type": "string", "example": "2 horas úteis" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/transactions": {
      "get": {
        "summary": "Lista conversões USDT",
        "description": "Histórico de envios USDT confirmados, com TX hash on-chain.",
        "tags": ["Conversões USDT"],
        "parameters": [
          { "name": "status",      "in": "query", "schema": { "type": "string", "enum": ["sent", "confirmed", "pending"] } },
          { "name": "charge_id",   "in": "query", "schema": { "type": "string" } },
          { "name": "external_id", "in": "query", "schema": { "type": "string" }, "description": "Filtra pelo external_id da charge associada" },
          { "name": "network",     "in": "query", "schema": { "type": "string", "enum": ["TRON", "POLYGON", "BSC"] } },
          { "name": "limit",       "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } },
          { "name": "offset",      "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data":   { "type": "array", "items": { "$ref": "#/components/schemas/Transaction" } },
                    "total":  { "type": "integer" },
                    "limit":  { "type": "integer" },
                    "offset": { "type": "integer" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/transactions/{id}": {
      "get": {
        "summary": "Consulta uma conversão USDT",
        "tags": ["Conversões USDT"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Transaction" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/webhooks/deliveries": {
      "get": {
        "summary": "Inspeciona disparos de webhook (auditoria)",
        "description": "Lista as últimas tentativas de entrega de webhook pra você debugar integrações. Cada linha é uma tentativa (até 3 por evento). Útil pra ver corpo enviado, status code retornado pelo seu servidor, latência e erro.",
        "tags": ["Webhooks"],
        "parameters": [
          { "name": "event",      "in": "query", "schema": { "type": "string", "enum": ["pix.received", "pix.expired", "pix.med", "usdt.sent"] } },
          { "name": "ok",         "in": "query", "schema": { "type": "boolean" }, "description": "true = só sucessos (2xx); false = só falhas" },
          { "name": "charge_id",  "in": "query", "schema": { "type": "string" } },
          { "name": "limit",      "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } },
          { "name": "offset",     "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data":   { "type": "array", "items": { "$ref": "#/components/schemas/WebhookDelivery" } },
                    "total":  { "type": "integer" },
                    "limit":  { "type": "integer" },
                    "offset": { "type": "integer" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/webhooks/test": {
      "post": {
        "summary": "Dispara webhook de teste",
        "description": "Envia um payload de teste pro `webhook_url` informado, com assinatura HMAC válida. Útil pra validar sua integração antes de criar cobranças reais. O evento sempre é `webhook.test`. Retorna o resultado da entrega (status code, latência, erro se houver).",
        "tags": ["Webhooks"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["webhook_url"],
                "properties": {
                  "webhook_url": { "type": "string", "format": "uri", "description": "URL HTTPS que vai receber o POST de teste" },
                  "event_type":  { "type": "string", "enum": ["pix.received", "pix.expired", "pix.med", "usdt.sent"], "default": "pix.received", "description": "Tipo do payload de exemplo a enviar" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado do disparo (sucesso ou falha — não bloqueia retorno)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok":          { "type": "boolean" },
                    "status_code": { "type": "integer", "description": "HTTP retornado pelo seu servidor (0 se timeout)" },
                    "duration_ms": { "type": "integer" },
                    "error":       { "type": "string", "nullable": true },
                    "signature_sent": { "type": "string", "description": "Assinatura HMAC enviada (pra você cruzar com seu validador)" },
                    "timestamp_sent": { "type": "string", "description": "Unix timestamp enviado no header" },
                    "payload_sent":   { "type": "object" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Alternativa ao Bearer. Aceita o mesmo formato `pk_live_*`."
      }
    },
    "schemas": {
      "Account": {
        "type": "object",
        "properties": {
          "id":    { "type": "integer", "example": 1 },
          "name":  { "type": "string",  "example": "Loja Exemplo" },
          "email": { "type": "string",  "format": "email" },
          "role":  { "type": "string",  "example": "admin" },
          "usdt_wallet": {
            "type": "object",
            "description": "Carteira USDT cadastrada (não-custodial — operador envia USDT direto pra cá após cada PIX pago)",
            "properties": {
              "address":    { "type": "string",  "nullable": true, "example": "TXyz...AbCd" },
              "network":    { "type": "string",  "nullable": true, "enum": ["TRON", "POLYGON", "BSC"], "example": "TRON" },
              "configured": { "type": "boolean", "description": "true quando address está preenchido" }
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "properties": {
          "gross_received_brl": { "type": "number", "example": 12483.50, "description": "Total bruto recebido em PIX (todo histórico)" },
          "pending_brl":        { "type": "number", "example": 347.00,   "description": "Cobranças pending aguardando pagamento" },
          "sent_usdt_brl":      { "type": "number", "example": 11250.00, "description": "Equivalente BRL do USDT já enviado pra sua carteira" },
          "total_fees_brl":     { "type": "number", "example": 374.50,   "description": "Soma das taxas da plataforma cobradas até hoje" },
          "net_brl":            { "type": "number", "example": 12109.00, "description": "Saldo líquido (gross - fees)" },
          "med": {
            "type": "object",
            "description": "Status do mecanismo de devolução PIX (MED)",
            "properties": {
              "debt_brl":             { "type": "number", "example": 0,    "description": "Débito acumulado por MEDs ativos" },
              "block_threshold_brl":  { "type": "number", "example": 200,  "description": "Limite a partir do qual a conta é bloqueada" },
              "account_blocked":      { "type": "boolean", "description": "true se conta bloqueada por MED — novas cobranças retornam 403 account_blocked_med" }
            }
          },
          "account_status": { "type": "string", "enum": ["active", "suspended", "blocked_med"] }
        }
      },
      "CreateChargeRequest": {
        "type": "object",
        "required": ["amount"],
        "properties": {
          "amount":             { "type": "number", "minimum": 1, "maximum": 100000, "example": 49.90, "description": "Valor em reais (BRL). Limite absoluto da API é R$ 100.000, mas existe limite per-conta (max_charge_brl, default R$ 500). Ultrapassar retorna 400 amount_above_limit." },
          "expires_in_minutes": { "type": "integer", "minimum": 5, "maximum": 1440, "default": 30, "description": "Tempo até o QR expirar. Default 30. Valores abaixo de 30 são automaticamente elevados pra 30 (piso silencioso) pra evitar pix.expired prematuro antes do pix.received quando o PSP confirma o pagamento atrasado." },
          "external_id":        { "type": "string", "maxLength": 120, "example": "pedido-12345", "description": "ID do seu sistema. Funciona como chave de idempotência (janela 24h). Alternativa ao header `Idempotency-Key`." },
          "description":        { "type": "string", "maxLength": 255, "example": "Plano Premium mensal" },
          "webhook_url":        { "type": "string", "format": "uri", "maxLength": 500, "example": "https://seu-sistema.com/webhook/liquibr", "description": "URL de callback assinada com HMAC-SHA256. Recebe pix.received, pix.expired, pix.med e usdt.sent." },
          "customer": {
            "type": "object",
            "properties": {
              "name":     { "type": "string", "example": "João Silva" },
              "email":    { "type": "string", "format": "email" },
              "document": { "type": "string", "example": "12345678901" }
            }
          }
        }
      },
      "Charge": {
        "type": "object",
        "properties": {
          "id":           { "type": "string", "example": "1842" },
          "external_id":  { "type": "string", "nullable": true, "example": "pedido-12345" },
          "status":       { "type": "string", "enum": ["pending", "paid", "expired", "failed", "refunded"] },
          "amount":       { "type": "number", "example": 49.90 },
          "amount_cents": { "type": "integer", "example": 4990 },
          "idempotent_replay": { "type": "boolean", "description": "Presente apenas em respostas 200 (replay) — confirma que essa charge não foi criada agora, é a mesma de uma chamada anterior com o mesmo external_id/Idempotency-Key." },
          "fees": {
            "type": "object",
            "properties": {
              "platform_fee_pct":   { "type": "number", "example": 3.0 },
              "percentage_fee_brl": { "type": "number", "example": 1.50 },
              "fixed_fee_brl":      { "type": "number", "example": 0.55, "description": "Taxa fixa aplicada em todas as cobranças, independente do valor" },
              "total_fee_brl":      { "type": "number", "example": 2.05 },
              "net_brl":            { "type": "number", "example": 47.85 }
            }
          },
          "pix": {
            "type": "object",
            "properties": {
              "qr_code":            { "type": "string", "description": "Código PIX copia-e-cola (BR Code EMV)" },
              "qr_code_base64":     { "type": "string", "nullable": true, "description": "String PRONTA pra <img src=>. Pode ser data URI (data:image/png;base64,...) ou URL externa. NÃO concatene 'data:image/png;base64,' antes — duplica o prefixo." },
              "qr_code_image_url":  { "type": "string", "nullable": true, "description": "Alias de qr_code_base64 (mesmo valor, nome explícito)." },
              "qr_code_base64_raw": { "type": "string", "nullable": true, "description": "Base64 PURO sem prefixo. Use só se for montar o data URI manualmente." },
              "copy_paste":         { "type": "string", "description": "Mesmo conteúdo de qr_code (alias)" }
            }
          },
          "payer": {
            "type": "object",
            "nullable": true,
            "properties": {
              "name":     { "type": "string" },
              "document": { "type": "string" }
            }
          },
          "expires_at":  { "type": "string", "format": "date-time" },
          "paid_at":     { "type": "string", "format": "date-time", "nullable": true },
          "created_at":  { "type": "string", "format": "date-time" },
          "psp_id":      { "type": "string", "nullable": true },
          "webhook_url": { "type": "string", "nullable": true }
        }
      },
      "ChargeListItem": {
        "type": "object",
        "properties": {
          "id":          { "type": "string" },
          "external_id": { "type": "string", "nullable": true },
          "status":      { "type": "string" },
          "amount":      { "type": "number" },
          "created_at":  { "type": "string", "format": "date-time" },
          "paid_at":     { "type": "string", "format": "date-time", "nullable": true },
          "expires_at":  { "type": "string", "format": "date-time" }
        }
      },
      "Transaction": {
        "type": "object",
        "description": "Conversão BRL→USDT realizada. Cada PIX paid gera uma transaction quando o operador envia o USDT on-chain.",
        "properties": {
          "id":              { "type": "string" },
          "charge_id":       { "type": "string", "nullable": true },
          "external_id":     { "type": "string", "nullable": true, "description": "external_id da charge associada" },
          "psp_id":          { "type": "string", "nullable": true },
          "amount_brl":      { "type": "number", "description": "Valor PIX original em reais" },
          "usdt_amount":     { "type": "number", "description": "Quantidade de USDT enviada" },
          "rate_brl":        { "type": "number", "description": "Cotação BRL/USDT no momento" },
          "spread_pct":      { "type": "number" },
          "network":         { "type": "string", "enum": ["TRON", "POLYGON", "BSC"] },
          "to_address":      { "type": "string", "description": "Carteira de destino do lojista" },
          "tx_hash":         { "type": "string", "nullable": true },
          "explorer_url":    { "type": "string", "nullable": true, "description": "Link direto pro block explorer da rede" },
          "network_fee_usd": { "type": "number" },
          "status":          { "type": "string", "enum": ["pending", "sent", "confirmed", "failed"] },
          "sent_at":         { "type": "string", "format": "date-time", "nullable": true },
          "confirmed_at":    { "type": "string", "format": "date-time", "nullable": true },
          "created_at":      { "type": "string", "format": "date-time" }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Payload enviado para o webhook_url. Validação anti-replay obrigatória: cliente DEVE verificar (1) `X-Webhook-Timestamp` dentro de ±5min e (2) `X-Webhook-Signature` == HMAC-SHA256 de '{timestamp}.{body_bruto}' usando webhook_secret, comparação em tempo constante.\n\nHeaders sempre enviados: `X-Webhook-Signature`, `X-Webhook-Timestamp`, `X-Webhook-Source: pixusdt`, `X-Webhook-Id: <uuid>` (use pra deduplicar caso o mesmo evento chegue duas vezes via retry).",
        "properties": {
          "event":     { "type": "string", "enum": ["pix.received", "pix.expired", "pix.med", "usdt.sent", "webhook.test"], "description": "`pix.med` dispara quando o operador marca devolução PIX no painel — sua integração deve reverter o pedido. `webhook.test` só é enviado via endpoint POST /webhooks/test." },
          "timestamp": { "type": "string", "format": "date-time" },
          "data": {
            "type": "object",
            "properties": {
              "charge": {
                "type": "object",
                "properties": {
                  "id":             { "type": "integer" },
                  "external_id":    { "type": "string", "nullable": true },
                  "amount_brl":     { "type": "number" },
                  "status":         { "type": "string" },
                  "paid_at":        { "type": "string", "nullable": true },
                  "created_at":     { "type": "string" },
                  "psp_id":         { "type": "string", "nullable": true },
                  "payer_name":     { "type": "string", "nullable": true },
                  "payer_document": { "type": "string", "nullable": true }
                }
              },
              "transaction": {
                "type": "object",
                "nullable": true,
                "description": "Presente em evento usdt.sent",
                "properties": {
                  "id":            { "type": "integer" },
                  "usdt_amount":   { "type": "number" },
                  "rate_brl":      { "type": "number" },
                  "network":       { "type": "string", "enum": ["TRON", "POLYGON", "BSC"] },
                  "to_address":    { "type": "string" },
                  "tx_hash":       { "type": "string" },
                  "network_fee":   { "type": "number" },
                  "explorer_url":  { "type": "string" },
                  "batch":         { "type": "boolean", "description": "true se foi parte de envio em lote" },
                  "batch_size":    { "type": "integer", "nullable": true },
                  "status":        { "type": "string" }
                }
              },
              "med": {
                "type": "object",
                "nullable": true,
                "description": "Presente em evento pix.med — descreve a devolução acionada pelo pagador no banco. Sua integração deve marcar o pedido associado como cancelado/estornado.",
                "properties": {
                  "id":         { "type": "integer" },
                  "amount_brl": { "type": "number" },
                  "reason":     { "type": "string" },
                  "evidence":   { "type": "string", "nullable": true },
                  "marked_at":  { "type": "string", "format": "date-time" }
                }
              }
            }
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id":            { "type": "integer" },
          "event":         { "type": "string" },
          "url":           { "type": "string" },
          "charge_id":     { "type": "integer", "nullable": true },
          "attempt":       { "type": "integer", "description": "1, 2 ou 3 (max retry)" },
          "status_code":   { "type": "integer", "description": "HTTP retornado pelo servidor de destino (0 = timeout/erro de conexão)" },
          "duration_ms":   { "type": "integer" },
          "ok":            { "type": "boolean", "description": "true se status 2xx" },
          "error":         { "type": "string", "nullable": true },
          "response_body": { "type": "string", "nullable": true, "description": "Corpo retornado pelo servidor (truncado a 4KB)" },
          "created_at":    { "type": "string", "format": "date-time" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error":   { "type": "string", "example": "validation_error" },
          "message": { "type": "string", "example": "Dados inválidos" },
          "details": { "type": "object" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "API key ausente, inválida ou revogada",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "Conta não pode operar. `error` pode ser:\n- `profile_incomplete` — falta cadastrar nome legal + WhatsApp\n- `account_suspended` — conta suspensa pelo operador\n- `account_blocked_med` — débito MED acima do threshold (consulte `/balance`)",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ValidationError": {
        "description": "Body inválido ou campo faltando (`error: validation_error`) OU valor acima do limite per-conta (`error: amount_above_limit`)",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Recurso não encontrado",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "IdempotencyConflict": {
        "description": "external_id (ou Idempotency-Key) existe nas últimas 24h mas com `amount` diferente. Use um external_id novo ou aguarde a janela expirar.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimit": {
        "description": "Limite de requisições excedido. Limite atual: **100 requisições/minuto por IP**. Header `Retry-After` indica em quantos segundos pode tentar de novo.",
        "headers": {
          "Retry-After":         { "schema": { "type": "integer" } },
          "X-RateLimit-Limit":   { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "PspError": {
        "description": "Erro no PSP que processa o PIX",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  },
  "tags": [
    { "name": "Conta",          "description": "Informações da conta autenticada" },
    { "name": "Cobranças",      "description": "Criar, consultar, listar e cancelar cobranças PIX" },
    { "name": "Conversões USDT","description": "Histórico de conversões BRL→USDT enviadas on-chain" },
    { "name": "Webhooks",       "description": "Auditoria e testes de webhooks outbound" }
  ]
}
