Documentação da API de Produtos

Esta página é para quem programa a integração entre o sistema da loja (ERP, PDV, controle de estoque) e o catálogo do OpaChat — normalmente um programador contratado pelo dono da loja, não o próprio dono. Ela foi escrita para você conseguir integrar sozinho, do início ao fim, sem precisar falar com o suporte da OpaTec.

1. Como conseguir uma chave de API

Toda chamada é autenticada por uma chave — não existe login, sessão nem senha de usuário nesta API. Quem gera a chave é o dono da conta OpaChat (só ele), na tela de Chaves de API do painel. Peça ao dono para gerar uma chave e te repassar o valor com cuidado — a chave só aparece em texto completo uma única vez, no momento em que é criada; depois disso, nem a OpaTec consegue recuperar o valor (só o hash fica gravado). Se a chave for perdida, a solução é gerar outra e revogar a antiga.

Toda conta pode ter mais de uma chave ao mesmo tempo — normalmente uma de produção e uma de teste (veja a seção 2). Cada chave tem sua própria cota de chamadas (seção 6) e seu próprio histórico, então uma chave revogada nunca afeta as demais.

A chave é da loja, não de um número. Ela vale para todos os números de WhatsApp que a loja tem no OpaChat. O estoque, não: cada número tem a sua prateleira — o seu próprio estoque. Por isso todo estoque que você mandar precisa dizer de qual número ele é (seção 4). Estoque sem o número é recusado.

2. Ambiente de teste (sandbox)

Toda chave nasce marcada como producao ou sandbox, e o prefixo do valor da própria chave já avisa qual é qual: opachat_live_... para produção, opachat_test_... para teste.

Isto não é um prefixo decorativo — a chave de teste isola o catálogo inteiro, não só o nome. Um produto importado com uma chave opachat_test_... vira uma linha totalmente separada do catálogo real, mesmo que você use o mesmo código (SKU) que pretende usar em produção. Ele nunca aparece nas consultas do catálogo real, nunca é enviado para o catálogo do WhatsApp/Meta, e pode conviver ao mesmo tempo com um produto de mesmo código do lado de produção, sem os dois se misturarem ou se sobrescreverem. Isso significa que você pode testar a integração inteira — inclusive reenviando o mesmo SKU várias vezes — sem nenhum risco de mexer no catálogo que os clientes finais realmente veem.

Use a chave de teste para desenvolver e validar sua integração; troque para a chave de produção só quando a integração já estiver pronta.

3. O endpoint

Um único endpoint recebe um lote de produtos por vez:

POST https://chat.opa360.com.br/api/v1/produtos

Exemplo completo, pronto para copiar e colar (troque a chave pela sua, e o numero pelo telefone do WhatsApp da loja):

curl -X POST https://chat.opa360.com.br/api/v1/produtos \
  -H "Authorization: Bearer opachat_test_SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "produtos": [
      {"codigo": "SKU-001", "ean": "7891000315507", "nome": "Dipirona 500mg 20 comprimidos", "preco": 12.90, "preco_promocional": 9.90, "numero": "5511999998888", "estoque": 35}
    ]
  }'

Resposta (testada de verdade contra o sistema, veja a nota no fim da página):

200 OK
{
    "aceitos": [
        {"codigo": "SKU-001", "nome": "Dipirona 500mg 20 comprimidos", "preco_centavos": 1290, "estoque": 35}
    ],
    "rejeitados": []
}

preco_centavos é o preço que você mandou, já convertido para centavos (12.90 vira 1290) — é assim que o OpaChat guarda preço internamente, para nunca perder centavo por arredondamento de ponto flutuante. E o estoque da resposta é o total da loja: a soma do que sobra para vender em todos os números.

Reenviar o mesmo código depois atualiza o produto (nome, preço, estoque) em vez de criar um duplicado — sua integração pode chamar este endpoint quantas vezes quiser, com o catálogo inteiro ou só com o que mudou, sem se preocupar em duplicar nada.

4. Campos de cada produto

Campo Tipo Regra
codigo texto Obrigatório — o SKU/código do seu sistema. É a chave que identifica o produto em reimportações.
nome texto Obrigatório.
preco número decimal Obrigatório, maior que zero. Envie um número decimal simples (ex.: 12.90) — nunca um texto com símbolo de moeda ("R$ 12,90") nem separador de milhar.
estoque número inteiro Opcional — o estoque de um número da loja, maior ou igual a zero (sem casas decimais). Só vale junto do numero, que diz de qual número ele é. Mandou estoque sem numero (e sem estoques)? O produto é recusado — o OpaChat não adivinha de qual número é o estoque.

Sem estoque, o produto entra normalmente e o estoque fica como está — é o jeito de atualizar só o nome, o preço ou a foto.
numero texto O telefone do WhatsApp da loja de que é o estoque, com DDD (com ou sem o +55, com ou sem máscara): "numero": "5511999998888", "estoque": 3. Precisa ser um número ligado da loja no OpaChat (um número desligado não recebe estoque); se não for, só aquele produto é recusado, com o motivo em rejeitados.
estoques lista Opcional — o estoque de vários números da loja de uma vez: [{"numero": "5511999998888", "estoque": 3}, {"numero": "5511988887777", "estoque": 2}]. Vale exatamente o que veio, número por número; os números que não vierem na lista ficam como estão. Com estoques, um estoque solto (sem numero) é ignorado.

Um número que não seja um número ligado da loja no OpaChat — ou o mesmo número duas vezes — recusa só aquele produto, com o motivo em rejeitados; o resto do lote entra.
preco_promocional número decimal Opcional — o "por" da promoção. Precisa ser menor que o preco: a Meta simplesmente não mostra a oferta quando ele é igual ou maior, e não avisa ninguém.

Não mandar o campo mantém a promoção que já existe (é o caso da sua sincronização diária). Para encerrar uma promoção, mande null.
promocao_comeca_em
promocao_termina_em
data e hora Opcionais, mas as duas juntas ou nenhuma — a Meta espera um intervalo fechado. Formato 2026-09-10 00:00:00.

Sem datas, a promoção vale até alguém encerrar.
promocao_nome texto Opcional — o nome da campanha ("Black Friday"). Serve para agrupar: no painel, uma campanha inteira se encerra num clique só.

Não precisa mandar. Sem ele, a promoção entra em "Sem campanha" e funciona exatamente igual — vale na vitrine, vai para a Meta, aparece no orçamento. Mandando, a campanha com esse nome é encontrada ou criada na hora.

Mandar vazio tira o produto da campanha, sem encerrar a campanha dos outros. Não mandar o campo mantém a campanha que ele já tem.
ean texto Opcional — o código de barras do produto (EAN-8, UPC-12, EAN-13 ou GTIN-14). Se mandar, ele é conferido: além do tamanho, o último dígito precisa fechar com os anteriores. Um código torto entra em rejeitados, com o motivo. Pode vir com espaço, ponto ou traço — nós limpamos.

Vale a pena mandar: ele vai para o catálogo do WhatsApp como gtin, que é como a Meta reconhece o produto.

O lote nunca é "tudo ou nada": cada produto é validado individualmente. Um preço inválido em um item não derruba o lote inteiro — só aquele produto entra em rejeitados, com um motivo em português, e os demais produtos do mesmo lote continuam sendo processados normalmente. Exemplo real, mandando "preco": "R$ 12,90" em vez de um número:

200 OK
{
    "aceitos": [],
    "rejeitados": [
        {
            "codigo": "SKU-002",
            "motivo": "Preço \"R$ 12,90\" inválido — envie um número decimal simples (ex.: 12.90), nunca um texto com símbolo de moeda ou separador de milhar."
        }
    ]
}

Só falta corrigir o campo apontado no motivo (aqui, preco) e reenviar — a resposta é sempre específica sobre qual produto e por quê, nunca um código de erro genérico sem explicação.

O estoque é de cada número

A loja pode ter mais de um número de WhatsApp, e cada um tem o seu estoque. Por isso o estoque sempre vai com o número: "numero" junto do "estoque" (um número), ou a lista "estoques" (vários de uma vez). Um produto que manda o estoque sem dizer o número volta assim, e só ele — o resto do lote entra:

200 OK
{
    "aceitos": [],
    "rejeitados": [
        {
            "codigo": "SKU-003",
            "motivo": "Informe de qual número de WhatsApp é este estoque: use \"estoques\": [{\"numero\": \"5511…\", \"estoque\": 3}] ou \"numero\" junto do \"estoque\"."
        }
    ]
}

5. O que fazer quando dá erro

Status Quando acontece O que fazer
401 Cabeçalho Authorization ausente, fora do formato, ou chave inválida/revogada. Confira se o cabeçalho está exatamente como Authorization: Bearer <chave>. Se a chave foi revogada, peça uma nova pelo painel — não existe forma de "reativar" uma chave revogada.
422 O JSON está incompleto — falta um campo obrigatório (produtos, ou codigo/nome/preco em algum item) ou veio com tipo errado (um numero que não é texto, por exemplo). É erro de forma, não de conteúdo — o lote inteiro é recusado, nenhum produto é gravado. Corrija a estrutura do JSON (a mensagem aponta o campo) e reenvie o lote inteiro.
200 Resposta normal — mas confira sempre a lista rejeitados, mesmo com status 200. Um preço ou estoque em formato errado — ou um estoque sem o número — gera 200, nunca 422 (ver seção 4). Os itens em aceitos já foram gravados. Corrija só os itens de rejeitados (o motivo já diz qual campo) e reenvie — reenviar o mesmo código nunca duplica.
429 Sua chave excedeu o limite de chamadas por minuto (seção 6). Espere alguns segundos e tente de novo. Se isso acontecer com frequência, agrupe mais produtos por chamada (o limite é por chamada, não por produto) em vez de mandar um lote pequeno várias vezes seguidas.
500 Falha interna do OpaChat, sem relação com o seu envio. Nenhum produto deste lote foi gravado. Tente novamente em instantes; se persistir, é um problema do nosso lado — sem necessidade de mudar nada na sua integração.

6. Limite de chamadas por minuto

Cada chave pode fazer até 60 chamadas por minuto. O limite é por chave, não por conta — duas chaves da mesma loja (por exemplo, uma de produção e uma de teste) nunca disputam a mesma cota entre si. Ao passar do limite, a chamada seguinte recebe 429 até a janela de um minuto renovar — não é preciso pedir aumento nem configurar nada; só espaçar as chamadas.

Além do limite da chave, cada endereço de internet pode fazer até 120 chamadas por minuto à API, somando todas as chaves que saem dele. Nesse caso o 429 vem com o cabeçalho Retry-After, que diz quantos segundos esperar.

7. Sincronização com o catálogo do WhatsApp

A resposta desta API (aceitos/rejeitados) reflete só a validação do OpaChat — se o formato do produto está certo — e é devolvida na hora, sem esperar a Meta. O envio do produto para o catálogo oficial do WhatsApp acontece à parte, em segundo plano, e nunca atrasa a sua chamada. Se em algum momento a Meta pedir para o envio esperar (ela tem os próprios limites de chamada, do lado dela), quem trata essa espera é o OpaChat — sua integração não precisa implementar nenhuma lógica de nova tentativa nem de intervalo de espera para isso.

8. Versionamento

O endpoint vive em /api/v1/.... Uma vez publicada, a v1 nunca quebra compatibilidade — nenhum campo existente muda de nome, de tipo, nem de significado. Se um dia for necessário uma mudança incompatível, ela nasce em /api/v2/..., e a v1 continua funcionando normalmente — sua integração não é obrigada a migrar.

Uma exceção, feita em 03/10/2026, antes de existir qualquer integração em uso: o estoque passou a ser de cada número de WhatsApp da loja, e o campo estoque enviado sem dizer o número passou a ser recusado (seção 4).

9. Enviar mensagem de WhatsApp

Outro site (o seu formulário de cadastro, por exemplo) pode pedir ao OpaChat para enviar uma mensagem de WhatsApp para uma pessoa. Para isso, o dono gera uma chave com a permissão "Enviar mensagens de WhatsApp" e escolhe o número da loja que envia. A chave só de produtos não envia mensagens (responde 403), e a chave só de mensagens não mexe no catálogo — as chaves geradas antes de 31/10/2026 são todas de produtos.

POST https://chat.opa360.com.br/api/v1/mensagens

curl -X POST https://chat.opa360.com.br/api/v1/mensagens \
  -H "Authorization: Bearer opachat_live_SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cadastro-123" \
  -d '{
    "celular": "5513998765432",
    "nome": "João",
    "modelo": "boas_vindas_captaqui",
    "idioma": "pt_BR",
    "variaveis": ["João"],
    "id_externo": "123",
    "email": "[email protected]",
    "empresa": "Padaria do João",
    "cnpj": "12.345.678/0001-90",
    "etiquetas": ["Captaqui", "Lead"],
    "autorizacao": {"em": "2026-10-31T10:00:00-03:00", "ip": "200.150.10.20", "texto": "Aceito receber mensagens no WhatsApp."}
  }'
CampoO que é
celularObrigatório. Só números, com o 55 e o DDD: 5513998765432. O mesmo celular com ou sem o nono dígito é a mesma pessoa — a ficha nunca é duplicada.
modeloObrigatório. O nome do modelo aprovado, como aparece na tela Modelos.
idiomaOpcional. Só é preciso quando o mesmo modelo está aprovado em mais de um idioma.
variaveisOpcional. Os valores de {{1}}, {{2}}… na ordem. Se faltar o {{1}}, vai o nome da pessoa; faltando outro, a chamada é recusada (422).
nome, emailOpcionais. Entram na ficha do cliente só onde ela está vazia — o que a loja anotou não é trocado.
empresa, cnpj, cpf, id_externoOpcionais. Ficam guardados no envio, para a loja consultar (cpf no lugar do cnpj para quem é autônomo). id_externo é o código do cadastro no seu site.
mensagemOpcional, até 2.000 caracteres. O recado da pessoa (por exemplo, a dúvida de um pedido de ajuda): vira nota interna na conversa e uma linha na observação da ficha, para a equipe ler antes de responder.
etiquetasOpcional. Até 10 nomes de etiqueta. Cada uma é marcada na ficha do cliente na hora em que o envio é aceito (mesmo que a mensagem não saia depois): se a loja já tem uma etiqueta com esse nome (sem diferença de maiúsculas), ela é usada; senão, é criada.
autorizacaoOpcional, e recomendado: a prova do aceite — em (data e hora em que a pessoa autorizou), ip e texto (o que ela aceitou). Fica guardada no envio; e, se a ficha ainda não tinha autorização, passa a ter.

Idempotency-Key (cabeçalho opcional, até 255 caracteres): mande um valor único por cadastro. Se a mesma chamada for repetida (a rede caiu, o seu site tentou de novo), a resposta é 200 com o MESMO envio — nada é enviado nem cobrado de novo, e nenhuma ficha é criada de novo.

Resposta:

202 Accepted
{"id": 41, "status": "aceito", "contato_id": 1520}

O envio acontece logo depois, em segundo plano. Para saber o que aconteceu, consulte pelo id:

GET https://chat.opa360.com.br/api/v1/mensagens/41

200 OK
{"id": 41, "status": "enviado", "motivo": null, "contato_id": 1520, "id_externo": "123",
 "mensagem": {"status_entrega": "entregue", "motivo_falha": null},
 "respondeu_em": "2026-10-31T10:12:40-03:00"}

status do envio: aceito (na fila), enviando, enviado, recusado (a pessoa pediu para não receber) ou falhou (o motivo diz por quê: sem saldo, número sem WhatsApp, número da loja desligado…). mensagem.status_entrega é o que o WhatsApp avisou depois: enviada, entregue, lida ou falhou. respondeu_em é a data e hora da PRIMEIRA mensagem que a pessoa mandou para o mesmo número depois deste envio — null enquanto ela não respondeu (ou quando a mensagem não chegou a sair). O que ela escreveu antes do envio não conta. Serve, por exemplo, para não mandar um segundo lembrete a quem já respondeu ao primeiro. A consulta só enxerga os envios da própria loja.

StatusQuando aconteceO que fazer
202Envio aceito.Guarde o id e consulte depois, se quiser saber o resultado.
200Repetição com a mesma Idempotency-Key.Nada a fazer: é o mesmo envio de antes.
401Chave ausente, inválida ou revogada.Confira o cabeçalho Authorization: Bearer <chave>.
403A chave não tem a permissão de mensagens, não tem número, está parada, ou a conta da loja está suspensa.A mensagem do erro diz qual. O dono gera uma chave com a permissão e o número na tela Chaves de API.
422Campo errado ou faltando, modelo que não existe ou não está aprovado, celular inválido, variável faltando — ou a pessoa pediu para não receber (o envio fica recusado).Leia erro.mensagem e erro.campos, corrija e mande de novo. Nada foi enviado nem cobrado.
429Passou do limite de chamadas por minuto (seção 6).Espere alguns segundos e tente de novo, com a mesma Idempotency-Key.

Exemplo de erro:

422
{"erro": {"mensagem": "O \"celular\" precisa ter só números, começando por 55, com DDD: 5513998765432 (12 ou 13 dígitos).",
          "campos": {"celular": ["O \"celular\" precisa ter só números, começando por 55, com DDD: 5513998765432 (12 ou 13 dígitos)."]}}}

Dúvida técnica sobre o formato aceito por esta API: os exemplos desta página foram executados de verdade contra o sistema antes da publicação. Esta documentação não substitui suporte para assuntos fora da própria API (contrato, cobrança, conexão do WhatsApp) — veja a Central de ajuda.