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\"."
}
]
}
- Produto sem estoque (só nome, preço, foto…) entra normalmente e não mexe no estoque de número nenhum.
- Produto novo aparece só nos números para os quais você mandou estoque. Sem estoque, ele fica cadastrado, mas em número nenhum, até você mandar o estoque com o número.
- A loja ainda não conectou o WhatsApp? Não há número para receber o estoque: os produtos com estoque são recusados, com o motivo "conecte o WhatsApp primeiro e mande o estoque com o número", e os sem estoque entram normalmente.
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.
- Só modelo aprovado pela Meta. A mensagem sai sempre de um modelo aprovado e em uso na tela Modelos da loja, na conta do número da chave — nunca texto livre. É regra do WhatsApp para falar primeiro com alguém.
- A resposta da pessoa cai na caixa de entrada do número da chave, como qualquer conversa. A mensagem enviada fica no histórico do cliente, sem entrar na fila dos atendentes.
- O custo sai do pacote ou do saldo da loja, pela categoria do modelo
(marketing, utilidade ou autenticação). Sem pacote nem saldo, a chamada é aceita, mas a mensagem não sai: o
envio fica
falhou, com o motivo, na consulta. - Quem pediu para não receber mensagens da loja é recusado — a loja religa na ficha do cliente, se ele autorizar de novo.
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."}
}'
| Campo | O que é |
|---|---|
| celular | Obrigató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. |
| modelo | Obrigatório. O nome do modelo aprovado, como aparece na tela Modelos. |
| idioma | Opcional. Só é preciso quando o mesmo modelo está aprovado em mais de um idioma. |
| variaveis | Opcional. Os valores de {{1}}, {{2}}… na ordem. Se faltar o {{1}}, vai o nome da pessoa; faltando outro, a chamada é recusada (422). |
| nome, email | Opcionais. Entram na ficha do cliente só onde ela está vazia — o que a loja anotou não é trocado. |
| empresa, cnpj, cpf, id_externo | Opcionais. 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. |
| mensagem | Opcional, 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. |
| etiquetas | Opcional. 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. |
| autorizacao | Opcional, 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.
| Status | Quando acontece | O que fazer |
|---|---|---|
| 202 | Envio aceito. | Guarde o id e consulte depois, se quiser saber o resultado. |
| 200 | Repetição com a mesma Idempotency-Key. | Nada a fazer: é o mesmo envio de antes. |
| 401 | Chave ausente, inválida ou revogada. | Confira o cabeçalho Authorization: Bearer <chave>. |
| 403 | A 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. |
| 422 | Campo 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. |
| 429 | Passou 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.