API e Webhooks
Tudo que você precisa para integrar, sem precisar de conta para ler. Cada cliente tem o próprio endereço de API, e a especificação OpenAPI fica no endereço dele.
1. Qual é o meu endereço
Não existe um host único de API. Cada conta é servida no próprio subdomínio, e ele já identifica o cliente: não há parâmetro de conta em nenhuma chamada, e uma credencial só funciona no endereço a que pertence.
Se a sua ouvidoria atende em domínio próprio, ele também serve. O endereço exato aparece em Configurações › API & Webhooks › Referência, já preenchido.
2. Autenticação e escopos
Token no header, em toda requisição:
A chave é criada pelo administrador da conta e mostrada uma única vez. Cada chave carrega os escopos que você escolher, e um escopo que falta devolve 403 dizendo exatamente qual.
| Escopo | Permite |
|---|---|
manifestations:read | Manifestações (leitura) |
manifestations:write | Manifestações (escrita) |
lgpd:read | LGPD (leitura) |
lgpd:write | LGPD (escrita) |
stats:read | Estatísticas (leitura) |
webhooks:manage | Webhooks (gerenciar) |
Uma chave com prefixo vxl_test_ autentica e lê, mas recusa qualquer escrita com o código test_key_readonly. Ela enxerga os mesmos dados reais: serve para desenvolver a integração sem risco de criar manifestação de verdade, e não é um ambiente separado.
3. A primeira chamada
4. Respostas e erros
Toda resposta tem a mesma forma. Sucesso traz ok, data e, nas listagens, meta. Erro traz code, message e request_id:
O request_id vem também no header X-Voxli-Request-Id, em toda resposta — inclusive nas de erro e nas sem credencial. Cite-o ao abrir um chamado: é por ele que a requisição exata é localizada.
Códigos possíveis:
| code | Significa |
|---|---|
delivery_pending | A entrega ainda está na fila. |
empty_patch | Nenhum campo para alterar. |
field_too_long | Campo acima do limite. |
idempotency_in_flight | Outra requisição com o mesmo Idempotency-Key está em andamento. |
insufficient_scope | A chave não tem o escopo. Veja `required_scope`. |
internal_error | Erro do lado da Voxli. |
invalid_destination | URL de webhook recusada (precisa ser https e pública). |
invalid_email | E-mail malformado. |
invalid_events | Evento de webhook desconhecido. |
invalid_json | O corpo não é JSON válido. |
invalid_parameter | Um filtro está ilegível. O campo `parameter` diz qual. |
invalid_status | Status fora do catálogo. |
invalid_token | A chave não existe nesta conta. |
invalid_type | O tipo de manifestação não existe ou está inativo. |
key_disabled | A chave foi desativada no backoffice. |
key_expired | A chave foi rotacionada e o prazo acabou. |
missing_description | Campo 'description' ausente. |
missing_events | Campo 'events' ausente ou vazio. |
missing_message | Campo 'message' ausente. |
missing_name | Campo 'name' ausente. |
missing_type | Campo 'type' ausente. |
missing_url | Campo 'url' ausente. |
module_unavailable | Módulo não disponível nesta instalação. |
not_found | O recurso não existe nesta conta. |
plan_required | O plano atual não inclui API & Webhooks. |
protocol_error | Não foi possível gerar protocolo único. Repita a requisição. |
rate_limited | Limite da chave excedido. |
tenant_suspended | Conta suspensa. |
terminal_status | A manifestação está num estado final. |
test_key_readonly | Chave vxl_test_ não escreve. |
too_many_attempts | Muitas tentativas sem credencial válida. |
unauthorized | Falta o header Authorization. |
5. Idempotência
Mande Idempotency-Key em qualquer escrita. Repetir com a mesma chave devolve a resposta guardada, com o header X-Voxli-Idempotent-Replay: true — nada é criado duas vezes, mesmo que você não saiba se a primeira chegou.
A chave vale para a conta inteira, não por credencial: dois serviços seus usando chaves de API diferentes continuam protegidos entre si. Se uma requisição com a mesma chave ainda estiver em andamento, a segunda recebe 409 idempotency_in_flight em vez de executar em paralelo.
6. Limites
| Itens por página | 50 no máximo. Pedir mais é limitado em silêncio; meta.per_page diz quanto veio. |
| Descrição e resposta | 20000 caracteres |
| Nome e e-mail de contato | 320 caracteres |
| Requisições | Definido por chave. Toda resposta traz X-RateLimit-Limit, -Remaining e -Reset; o 429 traz Retry-After. |
Filtro que a API não consegue interpretar devolve 400 nomeando o parâmetro. Ele nunca é ignorado em silêncio: um since malformado que devolvesse a base inteira seria pior que um erro.
7. Webhooks
Você configura um endereço https público e escolhe os eventos. Cada entrega chega como POST com o mesmo envelope:
| Header | Conteúdo |
|---|---|
X-Voxli-Event | Tipo do evento. |
X-Voxli-Delivery | UUID da entrega. É o MESMO em todas as tentativas — use para descartar repetição. |
X-Voxli-Attempt | Número da tentativa. |
X-Voxli-Timestamp | Unix timestamp desta tentativa. Entra no cálculo da assinatura. |
X-Voxli-Signature | sha256=<hmac> |
X-Voxli-Version | Versão do envelope. |
Como validar a assinatura
A assinatura é o HMAC-SHA256 de timestamp + ponto + corpo, com o secret do endpoint como chave. O corpo são os bytes crus da requisição: não desserialize e serialize de novo antes de calcular, porque qualquer diferença de espaço, ordem de chave ou escape de acento muda o hash.
Retentativa e duplicidade
Qualquer resposta 2xx é entrega concluída. Fora disso, tentamos 3 vezes, com intervalo de 2, 4 e 8 minutos. O timeout de cada tentativa é de 10 segundos: se o seu processamento demorar mais, responda 200 primeiro e processe depois.
Como a retentativa manda o mesmo corpo, guarde o X-Voxli-Delivery que você já processou e descarte o repetido. Redirect não é seguido, e o endereço precisa ser https e público — endereço interno é recusado.
Eventos disponíveis:
| Evento | Quando dispara |
|---|---|
manifestation.created | Manifestação criada |
manifestation.status_changed | Status alterado |
manifestation.assigned | Manifestação atribuída |
manifestation.replied | Resposta enviada |
manifestation.overdue | SLA vencido |
lgpd.request.created | Solicitação LGPD criada |
lgpd.request.status_changed | Status LGPD alterado |
test.ping | Evento de teste |
8. Especificação OpenAPI
A especificação completa fica no endereço da sua conta, sem exigir autenticação — dá para apontar um gerador de SDK antes mesmo de criar a primeira chave:
Ela é OpenAPI 3.1, já com o servidor certo em servers, e é verificada contra as rotas reais a cada build: um endpoint que exista e não esteja documentado, ou documentado e não exista, quebra a nossa integração contínua.
A API cobre manifestações, tipos, indicadores, solicitações LGPD e webhooks. Se o seu caso precisa de algo que não está aqui, fale com a gente: a lista cresce por pedido de quem integra.
Falar com a Voxli