> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pay.v4companyamaral.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> O que mudou na API, em ordem do mais recente para o mais antigo.

<Update label="8 de setembro de 2026" description="Documentação reorganizada">
  * O site ganhou duas abas — **Guias** e **Referência da API** — e páginas novas: [Aprovação de produção](/aprovacao-de-producao), [Cobranças](/cobrancas), [Links e checkout](/links-e-checkout), [Assinaturas](/assinaturas), [Modo de recebimento](/modo-de-recebimento), [Erros e códigos](/erros-e-codigos) e [Limites](/limites)
  * Os grupos da referência foram renomeados em português e ordenados por importância; as rotas internas do painel ficaram num grupo final **Interno**, documentadas por transparência
  * Textos desatualizados foram corrigidos contra a API atual (modo de recebimento, aprovação de produção, ambiente de testes, validade do token). A página Ambiente de testes mudou de endereço (`/devmode` redireciona)
</Update>

<Update label="7 de setembro de 2026" description="Aprovação de produção do lojista">
  * **Todo lojista nasce operando só em modo teste.** Para emitir em produção, o cadastro precisa ser aprovado pela equipe V4 Pay: dados da empresa completos + 3 documentos (CNPJ ou contrato social, documento do responsável, comprovante de endereço), enviados em **`/producao`** (novo). Enquanto não aprovado: sessão do painel sempre em teste, `POST /auth/environment` com `live` e `POST /api-keys` com `environment: live` respondem `409`, chave `v4pay_live_` existente responde `403`, e toda emissão em produção (Pix, checkout, link de pagamento, assinatura, cobrança manual e saque) é barrada — tudo com `codigo: PRODUCAO_NAO_LIBERADA` e o bloco `producao` dizendo em que pé está
  * O modo teste não muda em nada; pagamento de cobrança já emitida sempre confirma (a trava é só na emissão). No checkout público, o pagador de um link indisponível vê mensagem neutra (`codigo: LINK_INDISPONIVEL`) — a situação cadastral do lojista não é exposta
  * **Novo: `GET /admin/lojistas`** (e detalhe, download de documento por stream, aprovar e recusar) — a fila de análise, exclusiva da equipe. O usuário devolvido pelo login e pelo perfil ganha `production_status`
  * `PUT /auth/profile` passa a aceitar `address`, `address_number`, `complement`, `province`, `postal_code` e `company_type`
</Update>

<Update label="7 de setembro de 2026" description="Painel em domínio próprio">
  * O CORS da API aceita uma lista de origens (`FRONTEND_URL` + `FRONTEND_URLS_EXTRAS`) — o domínio novo `pay.v4companyamaral.com` e o antigo convivem durante a migração. Integrações sem navegador (curl, n8n, webhooks) seguem como sempre
</Update>

<Update label="7 de setembro de 2026" description="Roadmap público do produto">
  * **Novo: `GET /roadmap`** — o quadro do produto em 4 colunas (backlog, faremos, estamos cozinhando, pronto), com curtidas (1 por lojista, alterna em `POST /roadmap/{id}/vote`) e comentários (`GET`/`POST /roadmap/{id}/comments`; o autor ou a equipe apagam). Criar, editar, mover e apagar cartões é exclusivo da equipe V4 Pay (`403` com `codigo: SOMENTE_ADMIN` para os demais; chave de API nunca é admin)
  * O usuário devolvido pelo login e pelo perfil ganha `is_admin`
</Update>

<Update label="7 de setembro de 2026" description="Identificador fim a fim do Pix">
  * O `end_to_end_id` da cobrança passa a ser preenchido quando o pagamento real é confirmado: o aviso do Asaas não traz o E2E — ele mora na transação Pix, que a API agora consulta na confirmação (quando a chave tem a permissão necessária). Pagamentos antigos sem o campo podem ser completados pela operação
</Update>

<Update label="6 de setembro de 2026" description="Modo conta única pronto para produção">
  * No modo conta única (cobranças na conta principal, sem subconta): o `walletId` deixa de ser exigido (não há split); `GET /receiving-account` responde `modo: "conta_unica"` com status aprovado — não é preciso cadastrar empresa; `POST /withdrawals` responde `409` com `codigo: REPASSE_MANUAL` (o repasse é feito manualmente pelo V4 Pay enquanto o modo estiver ligado). Fora do modo, nada muda
</Update>

<Update label="6 de setembro de 2026" description="Servidor mais resiliente e log sem dado pessoal">
  * Erro inesperado em qualquer rota passa a responder `500` controlado com um código de correlação (`error.id`), em vez de poder derrubar o processo; a mensagem interna do erro não é mais ecoada ao cliente — erros de negócio (com status e mensagem próprios) continuam como sempre
  * Os logs do servidor deixam de registrar dados pessoais do pagador (documento, e-mail, telefone); passam a registrar identificadores (txid, lojista, modo, valor)
</Update>

<Update label="6 de setembro de 2026" description="Webhook com secret informado">
  * `POST /webhook-endpoints` aceita `secret` opcional — para o painel gerar o segredo no navegador e mostrá-lo ao lojista antes de salvar. Só no formato exato `whsec_` + 32 hexadecimais (a mesma entropia do gerado pela API); fora disso, `400`. Omitido, a API gera como sempre
</Update>

<Update label="6 de setembro de 2026" description="Cliente com CPF ou CNPJ">
  * Cliente, assinatura e cobrança aceitam **CPF ou CNPJ** no campo de documento (`cpf` / `customer_cpf`), com os dígitos verificadores conferidos — documento inválido é recusado com `400` e mensagem clara (antes o cliente gravava qualquer coisa sem validar). A API guarda só os dígitos
  * O comprovante público mascara CNPJ também: `12.***.***/0001-**`
</Update>

<Update label="6 de setembro de 2026" description="Cobrança a partir de produto">
  * `POST /pix` ganha o **modo produto**: com `product_id`, o valor é calculado no servidor (preço do produto, menos `coupon_code` opcional — mesmas regras do checkout), a descrição padrão é o nome do produto e a validade padrão é 24 h. O pagador vem de `customer_id` ou dos campos inline (`novo_cliente: true` também o cadastra em clientes). `success_url` e `cancel_url` (http(s) validadas) definem o retorno pós-pagamento, e `GET /checkout/{txid}` passa a devolvê-las com o `product_id`. O modo manual continua igual
</Update>

<Update label="6 de setembro de 2026" description="Campos que limpam de verdade">
  * Apagar a URL da imagem na edição do produto passa a limpar o campo: `PUT /products/{id}` com `image_url` vazio ou nulo grava nulo. URL preenchida agora precisa ser http(s) válida — texto solto ou outro esquema é recusado com `400`
  * O mesmo `''` limpa também: `description` de produto, cupom, assinatura e link de pagamento; `customer_email` da assinatura; `product_id` do link (desvincula o produto)
</Update>

<Update label="6 de setembro de 2026" description="Produto com sku">
  * `POST /products` e `PUT /products/{id}` aceitam `sku` (opcional, até 64 caracteres; vazio limpa). O campo era descartado em silêncio. Sku repetido entre os seus produtos do mesmo ambiente é recusado com `409` e `codigo: SKU_DUPLICADO`
</Update>

<Update label="6 de setembro de 2026" description="Assinatura nunca fica sem data de cobrança">
  * Uma assinatura ativa sem `next_billing_at` sumia do cron de cobrança em silêncio. Agora: `PUT /subscriptions/{id}` aceita `next_billing_at` mas recusa data nula ou inválida (`400`); reativar uma assinatura que ficou sem data recalcula a partir de agora pelo `interval`; e o cron corrige sozinho as que encontrar, com registro em `sem_data` no resumo da execução
</Update>

<Update label="6 de setembro de 2026" description="Cupom sem validade volta a validar">
  * Correção em `GET /coupons/validate`: cupom sem `expires_at` (sem validade — válido para sempre) era recusado como "não encontrado ou expirado". O checkout público não tinha o problema
</Update>

<Update label="6 de setembro de 2026" description="Correções da revisão de 06/09">
  * A exclusão de cupom, produto e assinatura inverteu a ordem: apaga primeiro e desvincula as referências depois. Falha no meio deixa referências órfãs — recuperáveis, avisadas no campo `aviso` da resposta — em vez de perder o registro com as referências já zeradas. Acima de 500 referências a exclusão é recusada (`409`, `codigo: MUITAS_REFERENCIAS`) com a sugestão de desativar em vez de apagar
  * Reenvio de webhook do Asaas não pode mais duplicar o e-mail de comprovante: o envio passou para depois de o evento ser marcado como concluído, e o timeout padrão do e-mail caiu de 10 s para 5 s
  * `GET /checkout/{txid}` ganha limite por IP (300 consultas por 10 minutos; o polling normal do checkout usa \~120) e responde `429` acima disso
  * Cupom que deixaria o total abaixo do mínimo de R\$ 5,00 é recusado já na validação (`400`, `codigo: VALOR_MINIMO`), em vez de prometer um total que a emissão recusaria em seguida
  * Desativar cupom passou a existir de verdade: `PUT /coupons/{id}` aceita `is_active`, e cupom desativado é recusado no checkout e no `/coupons/validate` — é a alternativa que o `409` da exclusão sugere
</Update>

<Update label="6 de setembro de 2026" description="QR gerado na hora">
  * A imagem do QR (`qr_code_url`) deixa de ser guardada na cobrança e passa a ser gerada a cada resposta a partir de `pix_code`. Continua vindo em `POST /pix`, `GET /pix/status`, `GET /checkout/{txid}` e no evento `charge.created`, mas só em cobrança `pending`; nas demais vem `null`. Motivo: era o maior campo da cobrança e deixava o banco sem espaço para campos novos
</Update>

<Update label="6 de setembro de 2026" description="Saldo sem teto silencioso">
  * O resumo de saldo (`GET /withdrawals/summary`) passa a somar **todas** as cobranças pagas e saques. Antes, uma conta com mais de 5 mil registros tinha o saldo disponível calculado para menos, sem erro. Listagens sem paginação continuam limitadas a 5 mil, mas agora avisam quando cortam
</Update>

<Update label="6 de setembro de 2026" description="Exclusões sem órfãos">
  * `DELETE /coupons/{id}`, `DELETE /products/{id}` e `DELETE /subscriptions/{id}` passam a zerar as referências antes de apagar: cobranças e links perdem o `coupon_id`, links perdem o `product_id`, cobranças perdem o `subscription_id`. A resposta traz `desvinculados` com a contagem por `tabela.campo`. Se a limpeza falhar, nada é apagado
</Update>

<Update label="6 de setembro de 2026" description="Comprovante por e-mail ao pagador">
  * Quando a cobrança vira paga, o pagador recebe um e-mail com valor, recebedor, data, identificador fim a fim e o link do comprovante. Vale para o pagamento real e para a simulação no Dev Mode. Cobrança de teste não gera e-mail, salvo `EMAIL_ENVIAR_EM_TESTE=true`. Depende de `RESEND_API_KEY` e `EMAIL_REMETENTE` no servidor; sem eles nada é enviado
</Update>

<Update label="6 de setembro de 2026" description="Recebedor congelado no comprovante">
  * Quando a cobrança vira paga, a API grava nela o nome e o CNPJ do lojista (`store_name`, `store_document`). O comprovante público (`GET /checkout/{txid}`) passa a mostrar quem recebeu **naquele dia**, mesmo que o cadastro mude depois. Cobranças pagas antes desta versão continuam lendo do cadastro atual
</Update>

<Update label="6 de setembro de 2026" description="Valor mínimo de cobrança">
  * `POST /pix`, o checkout de link, `POST /payment-links` e `POST /subscriptions` (criar e editar) passam a recusar valor abaixo de **R\$ 5,00** com `400` e `codigo: VALOR_MINIMO` (o mínimo vem em `valor_minimo`), antes de chamar o motor de pagamentos. Antes o erro chegava cru, depois de o cliente já ter sido criado. A regra vale igual no Dev Mode, para a recusa não aparecer só em produção
</Update>

<Update label="6 de setembro de 2026" description="Evento charge.created">
  * **Novo evento de webhook `charge.created`**: disparado sempre que uma cobrança nasce (`POST /pix`, checkout de link, cron de assinatura, `POST /charges`), com `pix_code`, `qr_code_url` e `checkout_url`. Serve para automações entregarem o QR ao cliente na hora
  * O catálogo de eventos (`GET /webhook-endpoints/eventos`) passa a listar todos os que a API emite: `charge.created`, `charge.paid`, `subscription.charged`, `withdrawal.completed`, `withdrawal.rejected`
</Update>

<Update label="6 de setembro de 2026" description="Checkout em modo teste, de ponta a ponta">
  * O checkout público (`/checkout/link/{slug}/pay`) passa a emitir a cobrança **no ambiente do link**: link criado em modo teste gera cobrança de teste. Antes, sem sessão, ele assumia produção e falhava com "lojista sem subconta"
  * `GET /checkout/link/{slug}` e `GET /checkout/{txid}` devolvem `environment`
  * **Nova rota** `POST /checkout/{txid}/simular-pagamento`, só para cobrança de teste: o ambiente de testes do motor de pagamentos dá a cobrança como paga e o resto do fluxo (webhook, `paid`, aviso ao lojista) acontece de verdade. É o botão "Simular pagamento" do checkout
  * Correção: o campo `qr_code_url` da cobrança tinha 2048 caracteres e a imagem do QR tem \~4,4 mil; a gravação falhava. O campo cresceu para 8192 e o provisionador passa a crescer campos quando o schema pede
</Update>

<Update label="6 de setembro de 2026" description="Webhooks com nome e eventos">
  * Endpoints de webhook ganham `name` e `events` (lista; vazia = todos). O despachante só entrega a um endpoint os eventos que ele assinou. `GET /webhook-endpoints/eventos` lista os eventos disponíveis. As respostas passam a trazer `version: v1`
</Update>

<Update label="6 de setembro de 2026" description="Assistente de primeiro acesso">
  * **Nova rota** `POST /auth/onboarding`: grava as respostas do assistente do painel (origem, responsável, loja, CNPJ, endereço, segmento, faturamento) e marca `onboarding_completed_at`. O usuário devolvido por `/auth/me` e pelo login passa a trazer esses campos
  * `PUT /auth/profile` aceita `accept_production_terms: true`, que registra `production_terms_accepted_at` (aceite dos termos no diálogo "Ir para produção")
</Update>

<Update label="6 de setembro de 2026" description="Erro de configuração sem detalhe interno">
  * Quando o motor de pagamentos do ambiente não está configurado no servidor, o erro devolvido por `POST /receiving-account` e `POST /pix` passa a ser uma frase curta em português, sem citar configuração interna. O diagnóstico completo continua no log do servidor
</Update>

<Update label="6 de setembro de 2026" description="Aviso de ambiente sem motor de pagamentos configurado">
  * `GET /receiving-account`, quando o lojista ainda não tem subconta, passa a dizer se o motor de pagamentos do ambiente está configurado no servidor (`provedor_configurado`). O painel usa isso para avisar **antes** do formulário, em vez de falhar no fim
</Update>

<Update label="5 de setembro de 2026" description="Aprovação da conta de recebimento (KYC)">
  **A API passa a acompanhar a aprovação da subconta no motor de pagamentos** e a barrar cobrança em conta não aprovada.

  * `GET /receiving-account` ganha o bloco `aprovacao`: as quatro frentes (dados comerciais, conta bancária, documentos, geral), a data da última conferência e o `link_documentos` para o lojista enviar documentos e selfie no motor de pagamentos
  * `status` da subconta passa a ser `creating | pending | approved | rejected` (era `active`, que só dizia que a subconta existia)
  * **Nova rota** `POST /receiving-account/approval` confere no motor de pagamentos na hora
  * `POST /pix` devolve **409** com `codigo: SUBCONTA_NAO_APROVADA` (ou `SUBCONTA_INEXISTENTE`) em vez de 500 com o erro cru do motor de pagamentos
  * O webhook da subconta passa a escutar os 18 eventos `ACCOUNT_STATUS_*`; subcontas antigas são completadas
</Update>

<Update label="5 de setembro de 2026" description="Validação da subconta no sandbox">
  **Subconta e split validados de ponta a ponta no sandbox** (conta PJ com autoaprovação).

  * O onboarding passa a garantir uma **chave Pix ativa** na subconta (`pix_key`, `pix_key_status` em `GET /receiving-account`) — sem ela o motor de pagamentos não gera QR code
  * Endereço público de webhook documentado para quem opera o servidor: sem ele a conta de recebimento nascia **sem aviso de pagamento**
  * `fee_amount` passa a gravar o valor **exato** do split que o motor de pagamentos devolve (`split[].totalValue`), calculado sobre o líquido
  * Correção: a trava de conta única respeitava o ambiente da requisição, não o da cobrança — no webhook (sem requisição) ela disparava para pagamento de teste
</Update>

<Update label="5 de setembro de 2026" description="Modo teste com porta de entrada">
  **O modo teste ficou acessível.** Até aqui ele existia no servidor sem jeito de ligar.

  * Novo: `POST /auth/environment` — troca a sessão de ambiente; `GET /auth/me` devolve `environment`; `POST /auth/refresh` preserva o ambiente
  * `POST /api-keys` aceita `environment`; a chave nasce `v4pay_test_…` ou `v4pay_live_…`
  * `GET /api-keys` mostra as chaves dos dois ambientes, com o ambiente de cada uma
  * Painel: interruptor **Modo teste**, faixa de aviso, seletor de ambiente ao criar chave, e o cadastro da empresa que abre a subconta
  * O interruptor **Dev Mode** passou a se chamar **Dados de exemplo**, para não se confundir com o modo teste
</Update>

<Update label="5 de setembro de 2026" description="motor de pagamentos e travas">
  **motor de pagamentos passa a ser o único provedor de Pix.** Cada lojista tem uma subconta no CNPJ
  dele, e a cobrança nasce lá com um split da taxa do V4 Pay. O Banco Inter foi removido.

  * Novo: `POST /receiving-account` e `GET /receiving-account` — cadastro da empresa e abertura da subconta
  * Novo: recepção do aviso de pagamento do motor de pagamentos, com validação de origem e idempotência
  * Novo: `POST /pix/simulate-payment` — marca uma cobrança de **teste** como paga
  * Novo: `POST /auth/google` — acesso com a conta Google
  * Novo: `GET /checkout/{txid}` — dados do comprovante para a página pública
  * Separação entre ambiente de **teste** e de **produção** por requisição (coluna `environment` em 11 coleções)
  * Correção: avisos ao lojista disparados sem `await` não completavam em serverless — agora completam
  * Correção: reenvio de webhook retoma evento que não concluiu, em vez de descartá-lo
  * Removido: `POST /webhook/pix` e `/admin/webhook` (eram do Inter)
</Update>

<Update label="31 de agosto de 2026" description="Faxina e segurança">
  * Removidas as sobras do Supabase; a camada de dados é 100% Appwrite
  * `.env` protegido no `.gitignore` e credenciais expostas auditadas
  * Guia de produção e serviço de Pix reescritos
</Update>

<Update label="30 de agosto de 2026" description="Perfil e auditoria">
  * Novo: `PUT /auth/profile` — nome da loja e CNPJ
  * Novo: `GET /audit` — trilha de auditoria das ações na loja
</Update>
