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

# Changelog

> Historico de mudancas e atualizacoes da YaVendio Business API.

<Update label="2026-10-13" tags={["Melhoria"]}>
  ## Mudanca de contrato: um pedido pode chegar sem endereco de entrega

  As lojas em breve poderao registrar vendas feitas fora do WhatsApp: na loja fisica ou por
  telefone. Quando o produto foi entregue em maos, o pedido nao tem endereco, e
  `GET /v1/orders` e `GET /v1/orders/{order_id}` o devolvem com **`address: null`**. Se a sua
  integracao le `address.full_address`, verifique antes que `address` nao e nulo.

  Um pedido tambem pode chegar **sem linhas** (`items: []`): foi registrado apenas a partir
  de um comprovante de pagamento ou de uma marcacao da loja, e ganha suas linhas depois com o
  mesmo `id`. Se voce sincroniza pedidos, leia-o de novo antes de considera-lo final.

  Dois valores novos que ja podem aparecer:

  * `payment_terms`: **`paid_pending_review`** — o cliente enviou o comprovante e a loja
    ainda nao o revisou. **Trate como nao pago** ate que mude.
  * `status` de produto nas respostas: **`unpublished`**. Criar ou editar um produto
    continua aceitando apenas `published`.
</Update>

<Update label="2026-09-07" tags={["Nova funcionalidade"]}>
  ## Um envio que a conta nao pode pagar agora responde `402`

  Os envios de WhatsApp sao pagos a partir de um saldo pre-pago. Ate agora, uma conta sem
  saldo podia continuar enviando pela API e a divida aparecia depois, sem ninguem ter sido
  avisado. A partir de hoje, `POST /v1/template-messages` responde **`402` e nao envia
  nada** quando a conta nao pode pagar a mensagem.

  O `type` diz qual dos tres casos e:

  * **`insufficient_funds`** — a conta ficou sem saldo para mensagens de saida.
  * **`wallet_sends_paused`** — os envios foram cortados quando o saldo acabou.
  * **`wallet_currency_mismatch`** — o saldo esta numa moeda que esta API nao consegue
    cotar. Tentar de novo nao muda nada; fale com a gente.

  Os dois primeiros se resolvem **recarregando na pagina de pagamentos do WhatsApp**, e o
  envio volta sozinho. Nao precisa de um `event_id` novo: o que recebeu o `402` continua
  livre, entao voce pode reenviar o mesmo evento depois de recarregar e nada e duplicado.

  **Tentar de novo sem recarregar devolve o mesmo `402`.** Trate como as outras recusas de
  conta: registre o `type` e nao fique tentando em loop.

  <Note>
    Isto **nao** muda nada enquanto sua conta nao estiver incluida na cobranca por saldo. Ate
    la a API responde exatamente como antes.
  </Note>
</Update>

<Update label="2026-09-03" tags={["Correção"]}>
  ## Templates com variáveis por nome já podem ser enviados

  Um template criado com marcadores nomeados — `Hola {{name}}, …`, que é o que o editor de
  campanhas escreve ao vincular uma variável a um campo do contato — aparecia em
  `GET /v1/templates` com `required_variables: []`, como se não tivesse variáveis, e qualquer
  envio era aceito com `202` para o provedor rejeitar depois. Agora:

  * **O catálogo publica as chaves reais.** `required_variables` lista posições (`["1", "2"]`)
    ou nomes (`["name"]`) conforme o template foi criado, e um novo campo `sendable` marca o
    template que mistura os dois estilos, que não pode ser enviado por esta via.
  * **`variables` aceita as mesmas chaves.** `{"name": "Ronald"}` sai como parâmetro nomeado.
  * **Um envio que o provedor rejeitaria é rejeitado antes, com `422`.** Uma chave faltando, uma
    sobrando ou uma posição num template nomeado não produzem mais um `202` vazio; a mensagem
    nomeia as chaves que o template espera.

  **Se a sua integração já envia posições em templates posicionais, nada muda.** Se enviava
  chaves a mais, agora recebe um `422` que antes se perdia no provedor.
</Update>

<Update label="2026-08-28" tags={["Nova funcionalidade"]}>
  ## O carrinho abandonado agora se ve e se desliga pelo painel

  Saber se os envios de um comercio estavam saindo obrigava a consultar `event_id` por
  `event_id`, e desliga-los nao tinha outra via alem de parar de chamar a API do ecommerce.
  As duas coisas agora se resolvem no painel de carrinho abandonado:

  * **Historico de envios.** Lista os envios do comercio, do mais recente ao mais antigo,
    com o estado de cada um, dentro da janela de retencao de 30 dias. Nao e mais preciso
    saber de antemao qual `event_id` procurar.
  * **Interruptor de ligar e desligar.** O envio pode ser desligado e ligado de novo sem
    tocar na integracao. Desligar nao apaga nada: o historico continua la.

  **Com o envio desligado, `POST /v1/template-messages` responde `200` com
  `"status": "discarded"` e `"status_detail": "disabled"`.** Nao e um erro: a requisicao
  funcionou e o envio esta desligado de proposito, igual ao que acontece numa conversa em
  pausa. O estado fica guardado sob o seu `event_id`, entao
  `GET /v1/template-messages/{event_id}` conta a mesma historia.

  **O seu ecommerce nao precisa de nenhuma mudanca.** Se voce ja aceita qualquer `2xx` e
  ramifica pelo `status`, um envio desligado entra por onde `stale` e `paused` ja entram.
  Esse catalogo de motivos pode crescer: registre o valor e nao ramifique de forma exaustiva
  por ele.
</Update>

<Update label="2026-08-28" tags={["Melhoria"]}>
  ## Enviar nao espera mais 10 segundos

  `POST /v1/template-messages` mantinha o request aberto por 10 segundos aguardando uma
  confirmacao de entrega **que nunca chegava por esse caminho**: esgotava a espera inteira
  e respondia `in_progress`. Esses dez segundos nao compravam nenhuma informacao. Agora a
  resposta chega assim que o servico de envio pega a mensagem. Tres coisas mudam para voce:

  * **Um envio aceito responde `202`, nao `200`.** Se a sua integracao trata so o `200`
    como sucesso, **ela passaria a ler envios bons como falhas**. Aceite qualquer `2xx`. O
    `200` fica para as respostas que nao produzem um envio novo: um `event_id` repetido e
    um envio descartado (`stale`, `paused`).
  * **O estado de um envio novo passa a ser `accepted`, nao `in_progress`.** Significam a mesma coisa
    — "a caminho, ainda nao sabemos como terminou" — e o desfecho continua sendo consultado
    em `GET /v1/template-messages/{event_id}`, que e o unico caminho: nenhum webhook
    reporta o resultado de um envio. `in_progress` continua sendo
    um valor valido do contrato: envios feitos antes desta mudanca ainda o devolvem
    enquanto estiverem dentro da janela de retencao de 30 dias.
  * **A modalidade de confirmacao rapida por conta acaba**, porque agora e o comportamento
    de todos. Nao ha nada a solicitar nem a configurar.

  O timeout recomendado do seu cliente cai de 45 para **35 segundos**: a chamada ao servico
  de envio mantem o seu teto de 30 segundos, e esse e agora o unico trecho que pode demorar.
</Update>

<Update label="2026-08-26" tags={["Correcao"]}>
  ## O estado de um envio agora diz como ele terminou

  `GET /v1/template-messages/{event_id}` respondia `in_progress` para sempre. `sent`,
  `delivered` e `read` estavam no contrato mas nunca chegavam, e `failed` so aparecia por
  uma recusa de conta. **Uma mensagem recusada pela Meta parecia igual a uma entregue.**
  Agora o estado e resolvido contra a mensagem real. Tres coisas mudam para voce:

  * **Os quatro estados ja chegam.** Se a sua integracao assumia que `in_progress` era o fim
    do caminho, voce passara a ver `sent`, `delivered`, `read` e `failed`. Ramifique pelo
    `status`.
  * **`status_detail` em um `failed` nao e uma lista fechada e pode estar ausente.** Alem de
    `account_not_eligible` e `no_sender_number`, agora pode trazer `send_failed` quando o
    envio nao chegou a se completar, `send_cancelled` quando foi cancelado antes de ser
    tentado, ou `provider_error_<codigo>` com o codigo da propria Meta quando o provedor
    recusou a entrega com um — por exemplo `provider_error_131049`, o limite de frequencia de modelos de
    marketing. Esse catalogo muda sem nos avisar: **nao o trate de forma exaustiva**,
    registre o valor e caia para uma falha generica se nao reconhece-lo ou se vier vazio.
  * **`updated_at` e quando o registro do envio mudou pela ultima vez**, nao quando voce
    consultou. Consultar ja nao o move, mas ele pode se mover sem que o estado mude, entao
    **nao serve como marca d'agua**: reconcilie pelo `event_id`.

  Os envios feitos antes desta mudanca tambem passam a responder corretamente, desde que
  ainda estejam dentro da janela de retencao.
</Update>

<Update label="2026-08-24" tags={["Correcao"]}>
  ## `GET /v1/templates` le o catalogo de onde os envios realmente saem

  A lista vinha de uma copia que o trilho de envio desta API nao escreve. Podia mostrar
  modelos que o envio depois recusava, e esconder os que voce podia mandar. Agora os dois
  leem o mesmo catalogo. Tres coisas mudam para voce:

  * **`language` nao pode mais vir vazio.** Antes diziamos que, se o modelo nao tivesse
    idioma registrado, qualquer codigo passava na validacao. Esse caso acabou: o catalogo
    registra o idioma sempre, entao **um idioma que nao bate e `422`, sem excecao**. Se voce
    dependia dessa saida, mande o codigo que a lista devolve.
  * **`category` agora chega em minusculas** — `marketing`, nao `MARKETING`. Ajuste se voce
    compara a string como esta.
  * **Sua lista pode mudar de tamanho, ate ficar vazia.** Os modelos vivem no catalogo do
    trilho de envio, preenchido quando sua conta sincroniza com o WhatsApp. Uma lista vazia
    significa que hoje todo envio e recusado — escreva para nos e sincronizamos.
</Update>

<Update label="2026-08-24" tags={["Melhoria"]}>
  ## Uma recusa de envio agora diz o que aconteceu

  `POST /v1/template-messages` respondia `502` para situações bem diferentes: uma conta não
  habilitada, uma conversa em pausa, um `event_id` reciclado. De fora todas pareciam
  idênticas, e a documentação pedia para repetir mesmo quando nenhuma retentativa podia
  mudar o resultado. Agora cada uma tem a sua própria resposta:

  * **Conta não habilitada para enviar** → `422` com `"type": "account_not_eligible"` em
    `detail[0]`. Não é transitório: fale conosco para habilitá-la.
  * **Sem número remetente conectado** → `422` com `"type": "no_sender_number"`. Conecte o
    número de WhatsApp da sua conta e repita.
  * **Conversa em pausa** → `200` com `"status": "discarded"` e
    `"status_detail": "paused"`. A requisição funcionou; o envio está desligado de
    propósito, porque um agente humano assumiu a conversa ou há um corte de faturamento.
  * **`event_id` já atrelado a outra mensagem, ou fora da janela de idempotência** → `409`
    com `"type": "idempotency_conflict"`. Mande o evento com um `event_id` novo: repetir o
    mesmo devolve o mesmo `409`.

  O `502` fica só para uma falha real do serviço interno de envio, essa sim vale repetir com
  backoff limitado.

  Com isso, **`failed` passa a ser alcançável**: as duas recusas de conta ficam guardadas
  sob o seu `event_id`, então `GET /v1/template-messages/{event_id}` conta a mesma história
  que o envio. Uma recusa de entrega ainda não produz esse estado. E um efeito da
  idempotência que vale ter em mente: reenviar o mesmo `event_id` depois de uma recusa de
  conta responde `200` com `"status": "failed"` — é o estado guardado, não um envio novo.
</Update>

<Update label="2026-08-24" tags={["Correcao"]}>
  ## O envio outbound agora e roteado por ambiente

  `POST /v1/template-messages` devolve `502` para qualquer envio que passe na validacao: a
  chamada interna vai para um servico que nao atende essa rota. Esta versao da a escrita o
  proprio endereco, configuravel por ambiente. **O envio comeca a funcionar quando esse
  endereco e apontado**, nao com esta publicacao.

  **Uma coisa nao vai mudar quando ligar**: o template enviado **nao aparecera** em
  `GET /v1/conversations/{id}/messages`. O envio e essa lista sao servidos por
  armazenamentos diferentes, entao reconcilie pelo seu proprio `event_id`, nao pela lista.

  A conexao com o banco tambem nao fica mais retida durante a espera do envio. Se voce
  fazia rajadas de envios, podia ver lentidao ou erros em outros endpoints da API durante
  essas rajadas; isso esta corrigido aqui.
</Update>

<Update label="2026-08-22" tags={["Nova funcionalidade"]}>
  ## Lista de templates aprovados

  Novo endpoint `GET /v1/templates`: devolve os templates que a sua conta pode enviar
  agora mesmo, com as variáveis que cada um espera. É o que faltava para montar um
  `POST /v1/template-messages` sem adivinhar os valores.

  Cada template traz:

  * **`name`** — o valor que vai em `template.name`.
  * **`language`** — o valor que vai em `template.language`. Pode vir vazio: o template
    não tem idioma registrado, mas o envio **ainda exige um código**, então não repasse o
    `null` — o request responde `422`. Só nesse caso qualquer código passa a validação; se o
    template registra idioma e o seu não corresponde, também é `422`. O código viaja tal qual
    para o WhatsApp, então errar o idioma faz o envio falhar lá — e hoje isso não se vê
    daqui: o estado fica em `in_progress`.
  * **`required_variables`** — as chaves que precisam estar em `variables`. `"1"` preenche
    `{{1}}`. Uma lista vazia significa que o template não leva variáveis.
  * **`category`** e **`display_name`** — este último é `null` a menos que difira do
    `name`, para reconhecê-lo do seu lado. A chave sempre vem: teste o valor, não a
    presença.

  Só aparecem os templates **aprovados**, porque são exatamente os que um envio aceita:
  listar um rascunho lhe daria um template que responde `422`. E se a lista voltar vazia,
  a sua conta ainda não tem nenhum template aprovado — cada envio vai responder `422` até
  que tenha.

  Escopo necessário: `outbound:read`.
</Update>

<Update label="2026-08-21" tags={["Correcao"]}>
  ## Correcao do contrato de Mensagens de Template

  A pagina de carrinho abandonado documentava `POST /v1/cart-abandonments`, um contrato que
  foi substituido durante o desenho e que **nunca existiu**. Ele foi substituido pelo
  contrato real, na mesma URL.

  Se voce comecou a construir contra a versao anterior, estas sao as mudancas que afetam
  voce:

  * **O caminho e `POST /v1/template-messages`**, e o estado e consultado em
    `GET /v1/template-messages/{event_id}`.
  * **O template viaja no request**, como `template: {name, language}`. A versao anterior
    dizia o contrario: que ficava fixado do nosso lado.
  * **`template_variables` agora se chama `variables`.**
  * **`first_name`, `last_name` e `second_last_name` nao existem.** Este endpoint nao cria
    nem atualiza o contato, e hoje nenhuma chamada publica faz isso: a API de Contatos e
    somente leitura.
  * **`occurred_at` e opcional.** Se voce omitir, o aviso nunca e considerado vencido.
  * **Novo campo opcional `metadata.use_case`** para atribuir o envio a um caso de uso.
  * **Escopos necessarios**: `outbound:send` e `outbound:read`.

  **Sua conta precisa estar habilitada para enviar**: enquanto nao estiver, cada chamada
  responde `502`. E os estados `sent`, `delivered`, `read` e `failed` ainda nao sao
  alcancaveis — eles estao no contrato para que a sua integracao ja os contemple.
</Update>

<Update label="2026-08-12" tags={["Nova funcionalidade"]}>
  ## Conversas, contatos, perfil da empresa e contagem de pedidos

  Quatro novos grupos de endpoints somente leitura:

  * **Conversas**: Lista as conversas do comercio (`GET /v1/conversations`, com filtro `sale_status`) e as mensagens de uma conversa (`GET /v1/conversations/{conversation_id}/messages`). Ler mensagens pela API nunca as marca como lidas.
  * **Contatos**: Lista os contatos do comercio com suas tags, status de opt-in e metadata (`GET /v1/contacts`).
  * **Empresa**: Perfil publico da empresa (`GET /v1/company`).
  * **Contagem de pedidos**: Total de pedidos e detalhamento por status (`GET /v1/orders/counts`).

  Novos scopes: `conversations:read`, `contacts:read`, `company:read` (a contagem usa `orders:read`).
</Update>

<Update label="2026-04-14" tags={["Melhoria", "Correcao"]}>
  ## Comprovantes de pagamento, resumo da conversa e correcao de midia

  * **Comprovantes de pagamento**: Novo campo `payment_receipt_urls` nas respostas de pedidos. Contem a lista de URLs das imagens de comprovantes de pagamento enviados pelo cliente (Yape, Plin, transferencia bancaria, etc.). Se houver varios comprovantes, todos sao incluidos.
  * **Resumo da conversa**: Novo campo `summary_conversation` nas respostas de pedidos. Contem um resumo gerado por IA com os detalhes do pedido e preferencias do cliente, evitando ter que entrar na conversa.
  * **Correcao de midia de variante**: Criar uma imagem de variante sem o campo `title` nao retorna mais erro. O campo e opcional conforme documentado.
</Update>

<Update label="2026-03-22" tags={["Nova funcionalidade"]}>
  ## Webhooks, fusos horarios e SKU

  * **Webhooks**: Receba eventos de pedidos em tempo real (`order.created`, `order.status_changed`). CRUD completo em `/v1/webhooks`. Assinatura HMAC-SHA256 em cada entrega.
  * **Fusos horarios**: Todas as datas (`order_date`, `created_at`) agora sao retornadas no fuso horario local do comerciante. Header `X-Timezone` incluido em cada resposta.
  * **SKU**: Novo campo `sku` em variantes de produtos e itens de pedidos.
  * **Variant ID**: Novo campo `variant_id` em itens de pedidos.
</Update>

<Update label="2026-03-20" tags={["Nova funcionalidade"]}>
  ## Gestao completa de produtos

  * **Produtos**: Criar, atualizar e excluir produtos (`POST`, `PATCH`, `DELETE /v1/products`)
  * **Variantes**: CRUD completo de variantes por produto
  * **Opcoes e valores**: Gestao de opcoes e seus valores por produto
  * **Midia**: Upload, atualizacao e exclusao de midia por variante
  * **Categorias**: Criar, atualizar, excluir categorias e atribui-las a produtos
  * **Catalogos**: Listagem de catalogos disponiveis (`GET /v1/catalogs`)
</Update>

<Update label="2026-03-10" tags={["Lancamento"]}>
  ## Lancamento da Business API

  Primeira versao publica da YaVendio Business API.

  * **Pedidos**: Listagem e detalhe de pedidos (`GET /v1/orders`, `GET /v1/orders/{order_id}`)
  * **Produtos**: Consulta de produtos (`GET /v1/products`, `GET /v1/products/{product_id}`)
  * **Autenticacao**: API Keys com header `X-API-Key`
  * **Rate Limiting**: Controle de taxa de requisicoes por API Key
  * **Paginacao**: Paginacao baseada em cursor para todas as listagens
</Update>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.