Ir para o conteúdo
Hugi Ajuda

A API do Hugi

É o caminho para quem já tem sistema e quer o Hugi como ponte: o WhatsApp entra e sai por aqui, e o atendimento acontece na tela que a sua equipe já usa.

Tudo abaixo usa uma chave de API no cabeçalho:

Authorization: Bearer hugi_sk_...

O tenant vem da chave. Se tenantId vier no corpo, a resposta é 400 — nunca um silêncio que faria você acreditar que o campo estava valendo.

O contrato completo, em OpenAPI, fica em GET /api/v1/openapi.json — e ele não pede chave, de propósito: documentação atrás de credencial é documentação que ninguém lê antes de decidir.

Você RECEBE por webhook, não por consulta em laço #

Este é o ponto que decide a arquitetura do seu lado, então ele vem primeiro.

Cadastre o endereço em Webhook de saída e o Hugi chama você quando algo acontece. Não fique consultando GET /atendimentos de minuto em minuto — você gastaria o limite de taxa para descobrir, atrasado, o que o webhook te contaria na hora.

Os eventos:

Evento Quando
mensagem.recebida o cliente escreveu — com o contato, a conversa, o protocolo e, se houver anexo, o endereço para baixar
mensagem.recibo o que você enviou foi aceito, entregue, lido ou falhou
mensagem.transcrita o texto de um áudio ficou pronto
atendimento.aberto uma conversa nova entrou
atendimento.finalizado uma conversa foi encerrada, com motivo e os dois tempos

Você escolhe quais quer no cadastro. Não marcar nenhum significa todos, inclusive os que ainda vamos criar.

Responder #

POST /api/v1/atendimentos/{id}/mensagens
Idempotency-Key: 6f1c0f9c-...
Content-Type: application/json

{ "texto": "Recebemos o seu exame, obrigado." }

Se você indexa por telefone e não guardou o nosso id, dá para mandar por número:

POST /api/v1/mensagens
{ "para": "5527999998888", "texto": "..." }

A resposta de sucesso é 202, e não 200: aceito não é entregue. Quem diz que chegou é o mensagem.recibo, depois.

A janela de 24 horas #

Fora dela, o WhatsApp não deixa mandar texto livre — e a recusa vem como 422 com o motivo:

{
  "erro": "envio_recusado",
  "mensagem": "A janela de 24h venceu. Fora de conversa aberta, use um template aprovado.",
  "detalhe": { "motivo": "janela_de_resposta_expirada" }
}

O caminho de dentro da janela é o texto; o de fora é o template:

{ "template": { "nome": "lembrete_de_consulta", "idioma": "pt_BR", "variaveis": ["Ana", "14h"] } }

O que a API NÃO faz, e por quê #

Não começa conversa com quem nunca escreveu. No WhatsApp quem começa é o cliente, e um contato do Hugi nasce quando ele manda a primeira mensagem. POST /mensagens para um número desconhecido devolve 422 SEM_ATENDIMENTO_ABERTO, em vez de criar um contato fantasma.

Não cria atendente nem empresa. Identidade é do Cert4All, e a conta não se administra por aqui.

Administrar a conversa #

POST /api/v1/atendimentos/{id}/finalizar    { "motivoId": "...", "observacao": "..." }
POST /api/v1/atendimentos/{id}/reabrir
POST /api/v1/atendimentos/{id}/transferir   { "paraFilaId": "..." }
POST /api/v1/atendimentos/{id}/transferir   { "paraAtendenteId": "...", "nota": "..." }
POST /api/v1/atendimentos/{id}/notas        { "texto": "..." }

Encerre o que você resolveu. Uma conversa que o seu sistema já tratou e que fica aberta aqui enche a fila, conta como conversa do mês e mantém o relógio de tempo de atendimento correndo sobre algo que ninguém está esperando.

transferir é o transbordo: quando o seu sistema não sabe responder, devolver para a equipe é melhor que encerrar uma conversa que o cliente ainda está esperando.

⚠️ Para uma pessoa, a nota é obrigatória. Para fila, não. A diferença é de efeito: a fila inteira vê a conversa e alguém escolhe pegá-la; uma pessoa recebe um atendimento nas mãos, e sem contexto ela recomeça a conversa do zero com o cliente.

Ler #

GET /api/v1/atendimentos?estado=&fila=&contato=&cursor=&limite=
GET /api/v1/atendimentos/{id}
GET /api/v1/atendimentos/{id}/mensagens?cursor=
GET /api/v1/atendimentos/{id}/eventos
GET /api/v1/contatos
GET /api/v1/conexoes
GET /api/v1/plano

Página por cursor, nunca por offset. Use o proximoCursor que veio na página anterior. Com offset, uma mensagem nova chegando entre duas páginas repete um item e pula outro — e a página 2 é lida justamente enquanto chegam mensagens.

Baixar um anexo #

O mensagem.recebida traz midia.url, algo como /api/v1/midias/<id-da-mensagem>. Chame com a sua chave e receba os bytes, com o content-type do arquivo.

O endereço da mídia dentro do WhatsApp nunca sai daqui: ele vem acompanhado das chaves de decifragem, e entregá-lo a você poria credencial nossa no seu código.

Limite de taxa #

Toda resposta traz:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 60

Passando disso, 429 com Retry-After. O limite é por chave, então o tráfego de uma integração não derruba a outra — mais um motivo para uma chave por sistema.

Erros #

Sempre a mesma forma:

{ "erro": "codigo_estavel", "mensagem": "frase que diz o que fazer", "detalhe": {}, "traceId": "..." }

Decida por erro, mostre mensagem, e cite traceId ao falar com a gente. A frase pode mudar quando alguém revisa o tom; o código, não.

detalhe.retentavel diz se repetir tem chance de dar certo.

Travar a versão #

Accept-Version: 1

Opcional. Mandando, você garante que um deploy nosso não muda o contrato debaixo do seu código: se um dia servirmos só a versão 2, você recebe 406 em vez de uma resposta em formato diferente.

Esta página foi revisada em 02 de setembro de 2026.