Teste grátis

Documentação da API

A API da Voza usa HTTPS, JSON e os verbos HTTP de sempre. Os corpos de mensagens, chamadas e webhooks seguem o formato da API oficial do WhatsApp (Meta), então o mesmo código atende os dois tipos de número da Voza.

Endereço base:

https://www.vozaapi.com/v1
Número por QR codeNúmero oficial (Meta)
Como conectaLendo um QR code com o celular, como no WhatsApp WebCom as credenciais da sua app da Meta (WhatsApp Cloud API)
Conta do WhatsAppWhatsApp ou WhatsApp Business comum; o celular continua funcionandoWhatsApp Business Platform, na sua conta da Meta
MensagensTexto e mídiasTexto, mídias, modelos, interativas, figurinhas, localização, contatos e reações
Janela de 24 horasNão háFora dela, só modelos aprovados pela Meta
ChamadasSim, várias ao mesmo tempo no mesmo númeroSim, quando a Meta libera as chamadas para o número
Cobrança das conversasNenhuma além do planoA Meta cobra na sua conta da Meta
RiscoO WhatsApp pode bloquear números que enviam spamSegue as políticas e a qualidade da Meta

Início rápido

  1. Crie uma conta e, em Painel › Chaves de API, uma chave.
  2. Crie um número:
curl https://www.vozaapi.com/v1/numbers \
  -H "Authorization: Bearer vz_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"name": "Loja Centro", "webhook_url": "https://seu-sistema.com/webhooks/whatsapp"}'
  1. Abra o qr_url da resposta e leia o QR code com o WhatsApp do celular (Aparelhos conectados › Conectar aparelho). Ou ligue um número oficial (veja Números oficiais).
  2. Envie a primeira mensagem:
curl https://www.vozaapi.com/v1/numbers/NUMBER_ID/messages \
  -H "Authorization: Bearer vz_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"to": "5511987654321", "type": "text", "text": {"body": "Olá! 👋"}}'
  1. As respostas do contato chegam ao seu webhook, no formato da Meta. Pronto: o resto desta página é detalhe.

Autenticação

Crie uma chave em Painel › Chaves de API e envie-a em todas as requisições, no cabeçalho Authorization:

Authorization: Bearer vz_SUA_CHAVE

Erros

Todos os erros têm o mesmo corpo. O código é estável e serve para o seu sistema decidir o que fazer; a mensagem é para pessoas e pode mudar.

A mensagem vem no idioma pedido no cabeçalho Accept-Language ou no parâmetro ?lang=: pt-BR, en ou es (outro idioma recebe inglês). Sem pedido, vem no idioma de quem criou a conta.

{
  "error": {
    "code": "invalid_request",
    "message": "O campo to é obrigatório.",
    "details": { "to": ["O campo to é obrigatório."] }
  }
}

Quando a falha vem da Meta, num número oficial, o erro traz também meta: o erro da Meta como ela o deu, com code (131047 fora da janela de 24 horas, 190 token inválido, 1380xx nas chamadas…), subcode, type, details e fbtrace_id. Quando vem do motor dos números por QR code, meta.reason diz por quê: not_on_whatsapp, lines_busy, lines_starting, restarting, not_linked, not_connected, timeout…

{
  "error": {
    "code": "whatsapp_refused",
    "message": "A Meta não aceitou o pedido: Message failed to send because more than 24 hours have passed.",
    "meta": {
      "code": 131047,
      "subcode": null,
      "type": "OAuthException",
      "details": "Message failed to send because more than 24 hours have passed since the customer last replied to this number.",
      "fbtrace_id": "AbCdEf123"
    }
  }
}
StatusCódigoQuando acontece
401invalid_api_keyA chave está ausente, é inválida ou foi revogada.
401invalid_webphone_tokenO token do telefone web está ausente, é inválido ou venceu.
403trial_expiredO teste grátis terminou: assine um plano no painel.
403subscription_inactiveA assinatura terminou (cancelada ou sem pagamento): assine de novo no painel.
403account_suspendedA conta está suspensa.
403number_limit_reachedA conta já tem todos os números do plano.
404not_foundO endereço ou o recurso não existe (ou é de outra conta).
404call_not_foundA chamada não existe ou já terminou.
405method_not_allowedO endereço existe, mas não com este método HTTP. O cabeçalho Allow diz quais valem.
409number_not_connectedO número por QR code não está conectado: leia o QR code antes de enviar ou ligar.
409meta_token_invalidA Meta recusou o token do número oficial (venceu, foi revogado ou não tem as permissões). Envie um novo em PUT /v1/numbers/{id}/meta.
409not_a_qr_numberO pedido só vale para números por QR code.
409not_an_official_numberO pedido só vale para números oficiais.
409send_in_progressOutro envio com a mesma Idempotency-Key ainda está em andamento.
409action_in_progressOutra ação de chamada com a mesma Idempotency-Key ainda está em andamento.
409call_answered_elsewhereOutro telefone web, o agente de voz ou o seu sistema já atendeu esta chamada.
409call_not_readyA chamada ainda está sendo preparada; tente de novo em instantes.
413media_too_largeA mídia recebida por um número por QR code passava de 64 MB e não ficou guardada.
422invalid_requestOs dados enviados são inválidos. O campo details lista os erros de cada campo.
422unsupported_typeO tipo de mensagem só existe nos números oficiais (modelo, interativa…).
422meta_refusedA Meta recusou as credenciais ao conectar um número oficial; a mensagem diz o motivo.
422whatsapp_refusedA Meta recusou a mensagem de um número oficial (modelo inexistente, parâmetro inválido…).
422not_on_whatsappO número chamado não tem WhatsApp.
422call_failedA Meta não aceitou a ação de chamada de um número oficial (sem permissão do contato, por exemplo).
429rate_limitedO limite de requisições da conta (espere os segundos de Retry-After) ou o limite de envios da Meta num número oficial (envie mais devagar).
429channel_limit_reachedA conta já está no limite de chamadas simultâneas do plano.
429lines_busyTodas as linhas do número estão em chamada.
4xxhttp_errorOutro erro do HTTP, como um corpo grande demais (413). O status diz qual.
500server_errorErro interno. Tente novamente; se o erro continuar, fale com a gente.
502whatsapp_refusedO WhatsApp não aceitou a mensagem de um número por QR code (o destino não tem WhatsApp, por exemplo).
502call_failedO WhatsApp não aceitou a ação de chamada de um número por QR code.
502media_unavailableO WhatsApp não entregou o arquivo de uma mídia recebida por um número por QR code.
503number_reconnectingO número por QR code está se reconectando ao WhatsApp. Tente novamente em instantes.
503engine_unavailableO serviço que mantém os números por QR code está indisponível. Tente novamente em instantes.
503meta_unavailableO pedido não chegou à Meta (sem conexão) ou a Meta respondeu que está indisponível: nada foi feito. Tente novamente em instantes.
503lines_startingAs linhas de chamada do número por QR code estão iniciando. Tente de novo em alguns segundos.
504send_timeoutO envio demorou demais. Repita com a mesma Idempotency-Key.
504meta_timeoutA Meta não respondeu a tempo depois de receber o pedido (um envio ou uma ação de chamada): ele pode ter sido feito ou não. Repita com a mesma Idempotency-Key.
504call_timeoutO serviço que mantém os números por QR code demorou demais para responder a uma ação de chamada.

Um 503 costuma passar em instantes: tente de novo, esperando um pouco mais a cada vez. Para saber se a Voza está fora, veja Status da Voza.

Limites

Idempotência

Ao enviar uma mensagem, mande o cabeçalho Idempotency-Key (até 128 letras, números e _ . : -). Repetir o envio com a mesma chave devolve a resposta do primeiro em vez de enviar de novo. Use-o sempre que o seu sistema for repetir um envio que falhou ou ficou sem resposta.

Idempotency-Key: pedido-4521-aviso-entrega

A Voza guarda a primeira resposta por 24 horas, nos dois tipos de número. Um envio repetido enquanto o primeiro ainda está em andamento espera por ele até 30 segundos; depois disso, a resposta é 409 send_in_progress, e basta tentar de novo com a mesma chave.

As ações de chamada (POST /v1/numbers/{id}/calls) aceitam o mesmo cabeçalho: repetir com a mesma chave devolve a primeira resposta em vez de repetir a ação (um connect não liga duas vezes). Uma repetida enquanto a primeira está em andamento recebe 409 action_in_progress.

Num número oficial, 503 meta_unavailable quer dizer que o pedido não chegou à Meta, e 504 meta_timeout, que chegou, mas a Meta não respondeu a tempo: a mensagem pode ter sido enviada (ou a ação feita). Nos dois casos, repita com a mesma Idempotency-Key: um erro não fica guardado, então a repetição vai à Meta; a Meta não tem idempotência própria, e depois de um meta_timeout a mensagem pode chegar duas vezes ao contato.

Números por QR code

Um número por QR code é um WhatsApp ou WhatsApp Business comum conectado como aparelho, como no WhatsApp Web. O celular continua funcionando normalmente.

O objeto número

{
  "id": "01jbk3m8x2c9q4r7t6v5w0y1z3",
  "name": "Loja Centro",
  "external_ref": "canal-981",
  "type": "qr",
  "status": "connected",
  "status_reason": null,
  "phone": "5511987654321",
  "qr_url": "https://www.vozaapi.com/qr/…",
  "webhook_url": "https://seu-sistema.com/webhooks/whatsapp",
  "agent_mode": "off",
  "connected_at": "2026-10-22T15:04:05Z",
  "created_at": "2026-10-22T15:01:10Z"
}
CampoDescrição
external_refA sua referência para o número (até 100 caracteres), como o id do canal no seu sistema. Volta nos webhooks, em metadata.external_ref, para você achar o canal sem guardar o id da Voza.
typeqr (QR code) ou meta (oficial).
status_reasonPor que o número está no status atual (veja Status); vazio quando não há o que explicar.
phoneOs dígitos do número, com o código do país, depois de conectado.
qr_urlUma página pública com o QR code, para mandar a quem está com o celular. Ela acompanha a conexão sozinha.
agent_modeQuando o agente de voz do número atende: off, always ou fallback (veja Agentes de voz).
connected_atQuando o número se conectou, em UTC (ISO 8601).

Status

StatusSignificado
startingA conexão está começando; o QR code aparece em seguida.
qrAguardando a leitura do QR code.
connectedConectado: o número envia e recebe mensagens e chamadas.
failedRecusado: pelo WhatsApp (um número bloqueado, por exemplo; use relink) ou, num número oficial, pela Meta (veja meta.error).

Se alguém desconectar a Voza no celular (Aparelhos conectados), o número volta para starting e depois para qr, com um QR code novo. Cada mudança chega ao webhook como number.status.

status_reason diz por que o número está no status atual, e uma mudança só do motivo também chega ao webhook:

status_reasonSignificado
logged_outO celular desconectou o aparelho (ou o número foi religado): é preciso ler o QR code de novo.
restartingO motor está reconectando o número, que continua ligado ao celular: espere, sem fazer nada.
failedO WhatsApp recusou o aparelho.
meta_refusedA Meta recusou o token ou o número oficial (veja meta.error).

Nos outros casos (qr, connected, um número novo) o motivo vem vazio.

Criar um número

POST /v1/numbers
{
  "name": "Loja Centro",
  "external_ref": "canal-981",
  "webhook": { "url": "https://seu-sistema.com/webhooks/whatsapp" }
}

Responde 201 com o número em starting. external_ref e webhook são opcionais (webhook_url também é aceito, no lugar de webhook.url). Com um webhook no pedido, a resposta traz também o segredo que assina os eventos, para conferir já o primeiro:

{
  "id": "01jbk3m8x2c9q4r7t6v5w0y1z3",
  "status": "starting",
  …
  "webhook": { "url": "https://seu-sistema.com/webhooks/whatsapp", "secret": "whsec_…" }
}

O endereço do webhook pode nomear o número: {id} vira o id do número na Voza e {external_ref}, a sua referência (codificada para URL; o pedido precisa trazer external_ref). O webhook fica definido antes de o número conectar, então nenhum evento se perde, sem um PUT depois.

{ "name": "Loja Centro", "external_ref": "canal-981", "webhook": { "url": "https://seu-sistema.com/webhooks/voza/{external_ref}?numero={id}" } }

Acima do limite do plano, responde 403 com number_limit_reached.

Mostrar o QR code

GET /v1/numbers/{id}/qr
{
  "status": "qr",
  "qr": "data:image/png;base64,iVBORw0KGgo…",
  "qr_url": "https://www.vozaapi.com/qr/…"
}

qr é uma imagem PNG pronta para um <img src>. O QR code muda a cada 20 segundos: consulte de novo a cada 3 a 5 segundos até o status ser connected (então qr vem vazio). Ou mande o qr_url a quem está com o celular.

Listar, consultar e excluir

GET /v1/numbers

Todos os números da conta, em {"data": [...]}. Com ?external_ref=canal-981, só os que têm essa referência.

GET /v1/numbers/{id}

O número, com o status lido na hora.

PATCH /v1/numbers/{id}

Muda o name ou a external_ref do número ("external_ref": null a remove) e responde com o número.

DELETE /v1/numbers/{id}

Desconecta o celular e exclui o número. Responde 204.

Conectar outro celular

POST /v1/numbers/{id}/relink

Desconecta o celular atual e começa de novo, com um QR code novo. Serve para trocar de celular ou para recuperar um número recusado.

Números oficiais

Um número oficial é um número da WhatsApp Cloud API, na sua conta da Meta. A Voza usa as credenciais da sua app: as mensagens e as chamadas passam pela API da Meta, e chegam ao seu webhook no mesmo formato dos números por QR code.

Antes de conectar, na Meta

  1. Tenha uma app da Meta com o produto WhatsApp e um número de telefone na conta do WhatsApp Business (WABA).
  2. Em WhatsApp › Configuração, assine os campos messages e calls do webhook da app (com qualquer endereço de webhook: a Voza aponta para si só este número, com uma substituição por número, e os outros eventos da sua app continuam onde estão).
  3. Crie um token permanente de um usuário do sistema, com as permissões whatsapp_business_messaging e whatsapp_business_management.
  4. Anote o ID do número de telefone e o ID da conta do WhatsApp Business (WhatsApp › Configuração da API), e a chave secreta da app (Configurações da app › Básico).

Conectar

POST /v1/numbers
{
  "name": "Atendimento oficial",
  "type": "meta",
  "webhook_url": "https://seu-sistema.com/webhooks/whatsapp",
  "meta": {
    "phone_number_id": "109876543210987",
    "waba_id": "102030405060708",
    "access_token": "EAAG…",
    "app_secret": "0123456789abcdef0123456789abcdef"
  },
  "calling": { "call_icon_visibility": "DISABLE_ALL" }
}

A Voza confere o número e o token na Meta, inscreve a sua app na conta, aponta os webhooks messages e calls do número para a Voza e ativa as chamadas. Se tudo der certo, responde 201 com o número já connected:

{
  "id": "01jbk4…",
  "type": "meta",
  "status": "connected",
  "phone": "5511912345678",
  "qr_url": null,
  "meta": {
    "phone_number_id": "109876543210987",
    "waba_id": "102030405060708",
    "display_phone_number": "+55 11 91234-5678",
    "verified_name": "Sua Empresa",
    "quality_rating": "GREEN",
    "calling": "enabled",
    "error": null
  }
}

Trocar o token

PUT /v1/numbers/{id}/meta
{ "access_token": "EAAG…novo", "app_secret": "(só se mudou)" }

Guarda as novas credenciais e conecta o número de novo. POST /v1/numbers/{id}/relink só verifica de novo, com as credenciais atuais. DELETE devolve os webhooks do número à sua app e o tira da Voza (o número continua na sua conta da Meta).

Nem a troca do token nem o relink mexem nas configurações de chamada: a Voza só as define quando o número é criado.

Configurações de chamada

GET /v1/numbers/{id}/calling
PUT /v1/numbers/{id}/calling

O objeto calling das configurações do número na Meta, lido e mudado como na Meta. No PUT, só os campos enviados mudam, e a resposta é {"success": true}.

{
  "calling": {
    "status": "ENABLED",
    "call_icon_visibility": "DEFAULT",
    "callback_permission_status": "ENABLED",
    "call_hours": {
      "status": "ENABLED",
      "timezone_id": "America/Sao_Paulo",
      "weekly_operating_hours": [{ "day_of_week": "MONDAY", "open_time": "0900", "close_time": "1800" }]
    }
  }
}
CampoDescrição
statusENABLED ou DISABLED: as chamadas do número.
call_icon_visibilityDEFAULT ou DISABLE_ALL: o botão de chamada na conversa.
callback_permission_statusENABLED ou DISABLED: pedir ao contato que ligou permissão para ligar de volta.
call_hours, call_iconsO horário de atendimento e os países que veem o botão, como na Meta, que os confere.

sip fica de fora: as chamadas passam pela Voza. Num número por QR code, a resposta é 409 not_an_official_number.

Enviar mensagens

O corpo é o mesmo da API oficial do WhatsApp. O número precisa estar conectado.

POST /v1/numbers/{id}/messages
curl https://www.vozaapi.com/v1/numbers/NUMBER_ID/messages \
  -H "Authorization: Bearer vz_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-4521" \
  -d '{
    "messaging_product": "whatsapp",
    "to": "5511987654321",
    "type": "text",
    "text": { "body": "Seu pedido saiu para entrega!" }
  }'
{
  "messaging_product": "whatsapp",
  "contacts": [{ "input": "5511987654321", "wa_id": "5511987654321" }],
  "messages": [{ "id": "3EB0C431D5A1B2C3" }]
}

Tipos de mensagem

TipoCorpoNúmeros
text{"text": {"body": "…"}} (até 4096 caracteres)todos
image, video{"image": {"link": "https://…", "caption": "…"}}todos
audio{"audio": {"link": "https://…"}}; com "voice": true, nota de voz (use OGG/Opus)todos
document{"document": {"link": "https://…", "filename": "nota-fiscal.pdf"}}todos
templateUm modelo aprovado: {"template": {"name": "…", "language": {"code": "pt_BR"}, "components": […]}}oficiais
interactiveBotões, listas, links e o pedido de permissão para ligar, como na Metaoficiais
sticker, location, contacts, reactionComo na API da Metaoficiais

A janela de 24 horas (números oficiais)

Num número oficial, a Meta só entrega mensagens livres a quem escreveu para o número nas últimas 24 horas. Fora dessa janela, comece a conversa com um modelo aprovado no WhatsApp Manager:

{
  "to": "5511987654321",
  "type": "template",
  "template": {
    "name": "pedido_enviado",
    "language": { "code": "pt_BR" },
    "components": [{ "type": "body", "parameters": [{ "type": "text", "text": "4521" }] }]
  }
}

Uma mensagem livre fora da janela é aceita pela API, mas chega ao webhook como status failed, com o erro da Meta.

Modelos de mensagem

GET /v1/numbers/{id}/templates

Os modelos da conta do WhatsApp Business de um número oficial, lidos na Meta, uma página por vez, na resposta da Meta. Cada modelo traz id, name, language, category, status e components, ou os campos pedidos em fields.

{
  "data": [{
    "id": "1234567890",
    "name": "pedido_enviado",
    "language": "pt_BR",
    "category": "UTILITY",
    "status": "APPROVED",
    "components": [{ "type": "BODY", "text": "Seu pedido {{1}} saiu para entrega." }]
  }],
  "paging": {
    "cursors": { "before": "MAZDZD", "after": "MjQZD" },
    "next": "https://www.vozaapi.com/v1/numbers/NUMBER_ID/templates?after=MjQZD"
  }
}

Botões

{
  "to": "5511987654321",
  "type": "interactive",
  "interactive": {
    "type": "button",
    "body": { "text": "Confirma a entrega amanhã às 10h?" },
    "action": { "buttons": [
      { "type": "reply", "reply": { "id": "confirm", "title": "Sim" } },
      { "type": "reply", "reply": { "id": "reschedule", "title": "Remarcar" } }
    ] }
  }
}

A escolha do contato chega ao webhook como uma mensagem do tipo interactive, com o id do botão.

Mídias

As mensagens recebidas com imagem, vídeo, áudio, documento ou figurinha trazem o id do arquivo (em image.id, audio.id…). Baixe-o com:

GET /v1/numbers/{id}/media/{media_id}

Webhooks

Cada número tem o seu webhook. A Voza envia para ele as mensagens recebidas, os status de entrega das mensagens enviadas, os eventos de chamada e as mudanças de status do número.

PUT /v1/numbers/{id}/webhook
curl -X PUT https://www.vozaapi.com/v1/numbers/NUMBER_ID/webhook \
  -H "Authorization: Bearer vz_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://seu-sistema.com/webhooks/whatsapp"}'
{ "url": "https://seu-sistema.com/webhooks/whatsapp", "secret": "whsec_…" }

O endereço precisa ser https e público. Envie "url": null para remover o webhook e, junto com a url, "rotate_secret": true para trocar o segredo. GET no mesmo endereço mostra a configuração atual.

Mensagens e status de entrega

Vêm no mesmo envelope da API oficial, então o código que lê os webhooks da Meta lê os da Voza:

{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "42",
    "changes": [{
      "field": "messages",
      "value": {
        "messaging_product": "whatsapp",
        "metadata": { "display_phone_number": "5511987654321", "phone_number_id": "NUMBER_ID", "external_ref": "canal-981" },
        "contacts": [{ "wa_id": "5521999990000", "profile": { "name": "Bruno" } }],
        "messages": [{
          "from": "5521999990000",
          "id": "ABCD1234",
          "timestamp": "1761150000",
          "type": "text",
          "text": { "body": "Olá, tudo bem?" }
        }]
      }
    }]
  }]
}

Um status de entrega vem no mesmo envelope, em statuses:

"statuses": [{
  "id": "3EB0C431D5A1B2C3",
  "status": "delivered",
  "timestamp": "1761150030",
  "recipient_id": "5521999990000"
}]

Status do número

{
  "object": "voza",
  "event": "number.status",
  "number": { "id": "NUMBER_ID", "name": "Loja Centro", "external_ref": "canal-981", "status": "starting", "status_reason": "logged_out", "phone": null },
  "timestamp": 1761150000
}

O evento test, enviado pelo painel, tem a mesma forma. status_reason diz por que o número mudou (veja Status).

Cabeçalhos e assinatura

CabeçalhoConteúdo
X-Voza-Signaturesha256= e o HMAC-SHA256 do corpo, com o segredo do webhook. O mesmo valor vai em X-Hub-Signature-256, como na Meta.
X-Voza-DeliveryO id da entrega, igual em todas as tentativas: use-o para ignorar repetições.
X-Voza-Eventmessages, calls, number.status ou test
User-AgentVoza-Webhooks/1.0

Verifique a assinatura com o corpo cru, antes de ler o JSON:

// PHP
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, getenv('VOZA_WEBHOOK_SECRET'));
if (! hash_equals($expected, $_SERVER['HTTP_X_VOZA_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}

// Node.js (body é o Buffer cru da requisição)
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.VOZA_WEBHOOK_SECRET).update(body).digest('hex');
const given = req.headers['x-voza-signature'] || '';
const valid = given.length === expected.length &&
  crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));

# Python (body são os bytes crus da requisição)
expected = 'sha256=' + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, request.headers.get('X-Voza-Signature', ''))

Respostas e novas tentativas

Telefone web

O jeito mais simples de atender e fazer ligações: um SDK em JavaScript, sem dependências, que roda no navegador dos seus atendentes. A chave de API fica no seu servidor; o navegador recebe só um token curto, de um número. Funciona com os dois tipos de número.

1. No seu servidor, peça um token para o atendente:

POST /v1/numbers/{id}/webphone-tokens
curl https://www.vozaapi.com/v1/numbers/NUMBER_ID/webphone-tokens \
  -H "Authorization: Bearer vz_SUA_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"label": "Ana", "minutes": 480}'
{ "token": "wp_…", "expires_at": "2026-10-23T01:00:00Z", "sdk_url": "https://www.vozaapi.com/sdk/webphone.js" }

minutes vai de 5 a 720 (8 horas por padrão). Use um token por aba do navegador.

2. Na página do atendente, o telefone pronto:

<script src="https://www.vozaapi.com/sdk/webphone.js"></script>
<voza-phone token="wp_…" lang="pt-BR"></voza-phone>

Ou a sua própria interface, com a API do SDK:

const phone = new VozaWebphone({ token: 'wp_…' });
phone.on('incoming', (call) => {
  // call.contact, call.contactName; call.accept() ou call.reject()
});
phone.on('call', (call) => console.log(call.direction, call.state));
phone.on('error', (error) => console.warn(error.code, error.message));
phone.on('disconnected', () => { /* o token venceu: peça outro */ });
await phone.connect();

const call = await phone.call('5511987654321');
call.mute(true);
call.hangup();

phone.disconnect(); // para de seguir as chamadas e desliga as que estão em curso
EventoQuando acontece
readyO telefone está pronto. Recebe a sessão, com o número (veja API do telefone web).
stateO telefone mudou de estado: connecting, ready ou closed.
incomingUma chamada recebida começou a tocar.
callQualquer mudança numa chamada: um novo estado ou o mudo.
endedUma chamada terminou; call.reason diz por quê.
monitorUma supervisão aberta com phone.monitor() mudou de estado (veja Supervisionar uma chamada da IA).
errorUm erro, com error.code: microphone_denied, microphone_unavailable, insecure_context, unsupported_browser, connection_failed, network_error ou um código da API, como not_on_whatsapp.
disconnectedO token venceu ou foi recusado: peça outro e crie um novo telefone.

Supervisionar uma chamada da IA

Um supervisor ouve, pelo navegador, uma chamada que o agente de voz atendeu, e pode assumi-la e devolvê-la à IA. Entrar só pede um token curto; assumir, devolver e sussurrar precisam da chave de API, então o SDK chama funções da sua página, e elas pedem ao seu servidor.

1. No seu servidor, peça um token de supervisão com POST /v1/numbers/{id}/calls/{call_id}/monitor e passe {token, ice_servers} ao navegador. Cada token entra uma vez, em até 5 minutos.

2. No navegador, com a API do SDK:

// a sua função que chama o seu servidor
const post = (path, body) => fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });

const monitor = await phone.monitor(callId, { // ou VozaWebphone.monitor(), sem telefone web
  token: 'mon_…',
  iceServers, // os ice_servers do token
  takeover: ({ callId, monitorId }) => post('/supervise/takeover', { callId, monitorId }),
  release: ({ callId, monitorId }) => post('/supervise/release', { callId, monitorId }),
  whisper: ({ callId, text }) => post('/supervise/whisper', { callId, text }),
});
monitor.on('state', (m) => console.log(m.state)); // listening, takeover, ended
await monitor.takeover(); // pede o microfone e fala com quem ligou
await monitor.whisper('Ofereça a sexta às 9h30.');
await monitor.release(); // a IA volta a falar
monitor.leave();

3. Ou no telefone pronto: com as funções em supervisor, uma chamada atendida pela IA mostra “Atendida pela IA” e o botão Ouvir; a supervisão tem Assumir, Devolver à IA, Mudo, Sair e um campo de instrução para a IA.

const element = document.querySelector('voza-phone');
element.supervisor = {
  token: (call) => post('/supervise/token', { callId: call.id }), // o seu servidor devolve {token, ice_servers}
  takeover: ({ callId, monitorId }) => post('/supervise/takeover', { callId, monitorId }),
  release: ({ callId, monitorId }) => post('/supervise/release', { callId, monitorId }),
  whisper: ({ callId, text }) => post('/supervise/whisper', { callId, text }),
};
// ou, para uma chamada que você escolheu:
element.monitor(callId, { token: 'mon_…', iceServers });

Chamadas pela API

Para quem prefere o seu próprio cliente WebRTC: as mesmas ações e eventos da API de chamadas da Meta. A Voza não grava as chamadas.

POST /v1/numbers/{id}/calls
AçãoCorpo
connectLiga para to com a oferta SDP do navegador em session (sdp_type: offer). Responde {"calls": [{"id": "…"}]}; a resposta SDP chega no evento connect.
pre_acceptPara uma chamada recebida: a resposta SDP do navegador (sdp_type: answer), para o áudio já se conectar.
acceptAtende a chamada no WhatsApp. Nos números oficiais, envie de novo a mesma resposta SDP em session.
reject, terminateRecusa ou encerra a chamada call_id.
{
  "action": "pre_accept",
  "call_id": "wacid.ABGG…",
  "session": { "sdp_type": "answer", "sdp": "v=0\r\no=- …" }
}

Permissão para ligar

GET /v1/numbers/{id}/call-permissions/{wa_id}

Se o número pode ligar para o contato agora. Num número oficial, é a resposta da consulta call_permissions da Meta; num número por QR code, sempre {"permission": {"status": "granted"}}, porque não há permissão a pedir.

{
  "messaging_product": "whatsapp",
  "permission": { "status": "temporary", "expiration_time": 1761236400 },
  "actions": [{ "action_name": "start_call", "can_perform_action": true, "limits": [ … ] }]
}

status é granted, temporary (vence em expiration_time) ou no_permission. wa_id é o número do contato, só com algarismos.

Eventos

Os eventos chegam no webhook do número com field: calls, no envelope da Meta: calls[] com event: connect (a oferta SDP de uma chamada recebida, ou a resposta de uma chamada feita) e event: terminate (com duration quando foi atendida), e statuses[] de uma chamada feita (RINGING, ACCEPTED, REJECTED). direction é USER_INITIATED numa chamada recebida e BUSINESS_INITIATED numa feita.

"value": {
  "messaging_product": "whatsapp",
  "metadata": { "display_phone_number": "5511987654321", "phone_number_id": "NUMBER_ID" },
  "contacts": [{ "wa_id": "5521999990000", "profile": { "name": "Bruno" } }],
  "calls": [{
    "id": "wacid.ABGG…",
    "from": "5521999990000",
    "to": "5511987654321",
    "event": "connect",
    "direction": "USER_INITIATED",
    "timestamp": "1761150000",
    "session": { "sdp_type": "offer", "sdp": "v=0\r\no=- …" }
  }]
}

Quando a chamada termina:

"calls": [{ "id": "wacid.ABGG…", "event": "terminate", "direction": "USER_INITIATED", "status": "COMPLETED", "duration": 126, … }]

Registro de chamadas

GET /v1/numbers/{id}/calls

As chamadas do número, das mais novas para as mais antigas: ?limit= de 1 a 200, 50 por padrão.

{
  "data": [{
    "id": 981,
    "call_id": "wacid.ABGG…",
    "direction": "incoming",
    "contact": "5521999990000",
    "contact_name": "Bruno",
    "status": "completed",
    "end_reason": null,
    "end_detail": null,
    "started_at": "2026-10-22T15:20:00Z",
    "answered_at": "2026-10-22T15:20:04Z",
    "ended_at": "2026-10-22T15:22:10Z",
    "duration": 126,
    "answered_by": "agent",
    "agent": { "agent_ref": "agent_42", "session_id": "…", "seconds": 124 }
  }]
}
CampoDescrição
call_idO id da chamada no WhatsApp, o mesmo dos eventos e das ações.
statusdialing, ringing, active, completed, missed, rejected, no_answer ou failed.
end_reasonchannel_limit: recusada por passar do limite de chamadas simultâneas. agent_unavailable: recusada porque o agente de voz não pôde atender. lost: ficou mais de 6 horas sem um fim e foi encerrada como failed. Vazio nas outras.
end_detailCom agent_unavailable, por que o agente recusou a chamada (veja Quem atende a chamada). Vazio nas outras.
answered_atQuando foi atendida; vazio numa chamada não atendida. Os horários são em UTC.
durationOs segundos de conversa; vazio numa chamada não atendida.
answered_byQuem atendeu: webphone, agent (o agente de voz) ou api (o seu sistema); numa chamada feita, quem ligou. Vazio numa chamada que ninguém atendeu.
agentNuma chamada do agente de voz: agent_ref, a sessão no motor e os segundos da IA (sem o tempo em que um supervisor estava com a chamada).

API do telefone web

O que o SDK usa por baixo, para quem faz o próprio telefone no navegador. Estes endereços aceitam o token do telefone web, não a chave de API, e respondem a páginas de qualquer domínio (CORS). O token vai em Authorization: Bearer wp_… ou, no fluxo de eventos, em ?token=, porque o EventSource não envia cabeçalhos.

GET /v1/webphone/session
{
  "session_id": "01jbk5…",
  "label": "Ana",
  "number": { "id": "01jbk3…", "name": "Loja Centro", "phone": "5511987654321", "status": "connected" },
  "ice_servers": [{ "urls": ["turn:turn.cloudflare.com:3478?transport=udp", …], "username": "…", "credential": "…" }],
  "expires_at": "2026-10-23T01:00:00Z"
}

ice_servers vai direto para new RTCPeerConnection({ iceServers }). Peça a sessão de novo antes de cada chamada: as credenciais do TURN vencem.

GET /v1/webphone/events

As chamadas do número, por Server-Sent Events. O evento calls traz o mesmo value do webhook de chamadas (contacts, calls e statuses, com o SDP). O evento claimed diz que alguém atendeu uma chamada, com call_id, session_id e by (webphone, agent ou api); se o session_id não for o seu, ela foi atendida em outro lugar.

const events = new EventSource('https://www.vozaapi.com/v1/webphone/events?token=wp_…');
events.addEventListener('calls', (event) => {
  const { calls, statuses } = JSON.parse(event.data);
});
events.addEventListener('claimed', (event) => {
  const { call_id, session_id, by } = JSON.parse(event.data);
});
POST /v1/webphone/calls

As ações de chamada no número do token, com o mesmo corpo de POST /v1/numbers/{id}/calls. Quando vários telefones recebem a mesma chamada, o primeiro pre_accept ou accept fica com ela, e os outros recebem 409 call_answered_elsewhere. O mesmo vale contra o agente de voz e contra o seu sistema com a chave de API (veja Agentes de voz).

Agentes de voz

Um agente de voz atende as chamadas de um número com IA: ouve, pensa e fala. Funciona nos números por QR code e nos oficiais, com a mesma API. A lógica é do seu produto: a Voza não guarda prompt nem configuração, só qual agente do seu produto atende o número e onde perguntar. No começo de cada chamada, o motor de agentes da Voza pede a configuração ao seu produto, e as ferramentas (agenda, base de conhecimento, mensagens) também são chamadas ao seu produto. Uma pessoa sempre pode ouvir, orientar a IA e assumir a chamada.

A Voza não guarda o conteúdo: a transcrição e o resumo vão para o seu produto nos eventos, e só ficam na Voza até a entrega. O motor de agentes guarda só o áudio das frases fixas do agente (a saudação, as frases de espera e as mensagens de handoff), para tocá-las sem esperar a voz sintética; uma frase que não toca há 30 dias sai, e apagar a conta apaga as dela. As chaves dos provedores de IA são as da sua conta.

O agente de um número

PUT /v1/numbers/{id}/agent
{
  "mode": "always",
  "fallback_after_seconds": 15,
  "agent_ref": "agent_42",
  "callback_url": "https://seu-sistema.com/voza/agents",
  "events_url": "https://seu-sistema.com/voza/events"
}
CampoDescrição
modealways: o agente atende toda chamada recebida. fallback: os telefones web tocam primeiro e, se ninguém atender em fallback_after_seconds (de 5 a 120, 15 por padrão), o agente atende. off: o agente só atende as chamadas que o seu produto entregar a ele.
agent_refO agente no seu produto (até 255 caracteres). A Voza o repete em cada pedido e evento.
callback_urlOnde o motor pede a configuração (agent.config) e chama as ferramentas (tool.call). https e público.
events_urlOpcional: para onde vão os eventos do agente. Sem ela, vão para o webhook do número.

Na primeira vez, a resposta traz agent.secret: ele assina os pedidos do motor ao seu produto e os eventos enviados a events_url. Guarde-o: GET no mesmo endereço mostra a configuração sem ele. POST /v1/numbers/{id}/agent/secret cria um novo (o anterior para de valer na hora) e DELETE desliga o agente e esquece o segredo.

{ "agent": { "mode": "always", "fallback_after_seconds": 15, "agent_ref": "agent_42", "callback_url": "https://…/voza/agents", "events_url": "https://…/voza/events", "secret": "agsec_…" } }

Quem atende a chamada

Uma chamada recebida fica com quem atender primeiro: um telefone web, o agente ou o seu sistema com a chave de API (pre_accept ou accept em POST /v1/numbers/{id}/calls). Os outros recebem 409 call_answered_elsewhere, e os telefones web recebem o evento claimed com by: webphone, agent ou api. O registro de chamadas mostra quem atendeu em answered_by.

Com o agente em always, logo que a chamada chega, o agente fica com ela e o motor responde à oferta SDP; os telefones web a veem tocar e, em seguida, o claimed. O webhook do número recebe os eventos calls como sempre. Se o motor não puder atender (o seu produto não mandou a configuração e não havia uma guardada, falta uma chave de IA…), a chamada é recusada e fica no registro com end_reason: agent_unavailable. Em fallback, ela continua tocando nos telefones web.

O seu produto também pode recusar uma chamada: se ele responder ao agent.config com 409 ou 410 e {"error": {"code": "agent_unavailable"}}, o motor não usa a configuração guardada e não atende. Em always, a chamada é recusada (end_reason: agent_unavailable); em fallback, ela continua tocando nos telefones web; entregue pela API, a resposta é 409 agent_unavailable.

Uma conta bloqueada (teste encerrado, assinatura encerrada ou suspensa) não tem agente, como não tem API nem telefone web: a chamada chega ao webhook do número e fica tocando até quem ligou desistir.

Quando o agente não pode ficar com uma chamada que toca, o seu produto recebe o evento call.agent.declined, com o motivo em reason, o código da recusa em detail, o modo do número em mode e, em outcome, o que aconteceu com a chamada: rejected (recusada, em always) ou ringing (continua tocando nos telefones web, em fallback). Uma chamada recusada fica no registro com end_reason: agent_unavailable e o motivo em end_detail. Entregue pela API, a chamada não gera o evento: o seu produto recebe o erro.

reasonQuando acontece
product_refusedO seu produto recusou a chamada (respondeu ao agent.config com 409 ou 410 e agent_unavailable).
config_unavailableO seu produto não enviou uma configuração válida a tempo, e o motor não tinha uma guardada.
missing_provider_keyFalta a chave de um provedor de IA que o agente usa.
invalid_offerO motor não conseguiu responder à oferta SDP.
at_capacity, drainingO motor de agentes está no limite de chamadas, ou reiniciando (a Voza pede de novo uma vez, dentro do mesmo prazo).
account_limitA conta já tem todas as sessões de agente simultâneas que pode ter (limits.agent_sessions em GET /v1/account).
runtime_unavailable, runtime_errorO motor de agentes não respondeu a tempo, ou falhou.
whatsapp_refusedA Meta ou o motor de QR recusou a resposta do agente (pre_accept).
errorOutra falha da Voza.

Cada conta tem um limite de sessões de agente ao mesmo tempo, somando as chamadas que os agentes atendem e as chamadas de teste, e um limite menor só para as chamadas de teste: limits.agent_sessions e limits.agent_tests em GET /v1/account (por padrão, 10 e 2; a Voza muda os de uma conta parceira). Acima dele, uma chamada em always é recusada (account_limit), uma em fallback continua tocando nos telefones web, e a API responde 429 agent_limit_reached.

POST /v1/numbers/{id}/calls/{call_id}/agent

Entrega ao agente uma chamada recebida que ainda está tocando: o seu produto decide chamada a chamada. {"agent_ref": "…"} é opcional (outro agente do seu produto). Responde {"success": true, "call": {…}}, ou 409 call_answered_elsewhere se alguém já atendeu e 409 call_not_ringing se ela já terminou. call_id é o id da chamada no WhatsApp; o id dela na Voza também vale.

O que o motor pede ao seu produto

Pedidos POST à callback_url, com X-Voza-Event (agent.config ou tool.call), X-Voza-Timestamp (segundos Unix) e X-Voza-Signature: sha256=…, o HMAC-SHA256 de {timestamp}.{corpo} (o timestamp, um ponto e o corpo como chegou) com o segredo do agente. Confira a assinatura e recuse um X-Voza-Timestamp a mais de 5 minutos do seu relógio: assim um pedido capturado não pode ser repetido depois.

$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);
$valid = hash_equals($expected, $signature) && abs(time() - (int) $timestamp) <= 300;

No começo de cada chamada, a configuração (responda em até 2 segundos):

{ "type": "agent.config", "agent_ref": "agent_42", "call": { "id": "01jbk5…", "direction": "incoming", "test": false }, "number": { "id": "NUMBER_ID", "phone": "5511987654321", "type": "qr" }, "contact": { "wa_id": "5521999990000", "name": "Bruno" } }
{
  "version": "42.3",
  "language": "pt-BR",
  "greeting": { "text": "Olá, aqui é a assistente virtual da Clínica Sorriso. Como posso ajudar?", "interruptible": false },
  "instructions": "Marque, remarque e desmarque consultas. Confirme a data em voz alta.",
  "guardrails": ["Nunca dê conselho médico."],
  "voice": { "provider": "elevenlabs", "voice_id": "…", "speed": 1.0 },
  "stt": { "provider": "deepgram", "language": "multi" },
  "llm": { "provider": "anthropic", "model": "claude-haiku-5-5" },
  "turn": { "interruption": "medium", "silence_ms": 600 },
  "tools": [{
    "name": "find_slots",
    "description": "Horários livres na agenda",
    "parameters": { "type": "object", "properties": { "day": { "type": "string" } }, "required": ["day"] },
    "filler": "Um instante, vou ver a agenda."
  }],
  "handoff": {
    "on_request": true,
    "message": "Vou passar para a equipe, um momento.",
    "timeout_seconds": 45,
    "timeout_message": "Ninguém da equipe pode atender agora. Posso anotar um recado?",
    "timeout_action": "resume"
  },
  "error_message": "Desculpe, estou com um problema técnico. Vou passar para a equipe.",
  "max_duration_seconds": 900
}

O motor guarda a última configuração de cada agente pela version: se o seu produto não responder a tempo, usa a guardada. Cada ferramenta é chamada com {"type": "tool.call", "call_id", "agent_ref", "tool", "arguments"} e tem 8 segundos para responder {"result": …}, com say (uma frase a dizer), end_call: true ou handoff: true opcionais. A base de conhecimento é uma ferramenta como outra qualquer.

Quando um provedor falha

Se a transcrição, a IA ou a voz falharem no meio de uma chamada (o provedor recusou a chave da conta, continua falhando, ou a IA não responde a um turno em 20 segundos), quem liga não fica em silêncio: o seu produto recebe call.agent.error, o agente diz error_message (ou, sem ela, a mensagem do handoff; se a própria voz falhou, só uma frase já guardada, ou então um toque) e passa a chamada para a equipe, se o agente tiver handoff.on_request, ou a encerra (reason: provider_error). A IA não volta a atender nessa chamada: se a equipe a devolver, ou ninguém a assumir, ela termina.

{ "service": "llm", "provider": "anthropic", "status": 401, "category": "authentication", "action": "handoff" }

service é stt, llm ou tts; status, o status HTTP do provedor, quando há um; category, o tipo de falha (authentication, rate_limit, quota, server, timeout…); action, handoff ou end.

Eventos do agente

Vão para a events_url do agente, assinados com o segredo do agente, ou para o webhook do número, assinados com o segredo do webhook. Têm object: voza e o nome do evento em event e em X-Voza-Event. Saem numa fila só deles, e os de uma chamada saem em ordem, um depois do outro: um evento que falhou segura os seguintes da mesma chamada até a nova tentativa. As novas tentativas são depois de 5, 15 e 30 segundos, por até 2 minutos; call.agent.ended (com a conversa inteira), call.agent.error e call.agent.declined continuam depois, pelo prazo dos eventos de mensagem.

{
  "object": "voza",
  "event": "call.transcript",
  "event_id": "5f2c…",
  "number": { "id": "NUMBER_ID", "name": "Loja Centro", "external_ref": null, "status": "connected", "status_reason": null, "phone": "5511987654321" },
  "call": { "id": "01jbk5…", "call_id": "wacid.ABGG…", "direction": "incoming", "contact": "5521999990000", "contact_name": "Bruno", "agent_ref": "agent_42", "agent_session_id": "…" },
  "test": false,
  "data": { "speaker": "caller", "text": "Quero remarcar a consulta de sexta.", "start": 4.2, "end": 6.9 },
  "timestamp": 1761150012,
  "timestamp_ms": 1761150012345
}
EventoQuandoEm data
call.agent.startedO agente atendeu.call, number, contact, agent_ref e a versão da configuração (config_version).
call.agent.stateA cada mudança.state: listening, thinking, speaking, handoff, handoff_timeout (ninguém assumiu a chamada a tempo) ou supervised.
call.transcriptA cada fala completa.speaker (caller, agent ou supervisor), text, start e end em segundos desde o atendimento.
call.agent.tool_callA cada ferramenta.name, duration_ms e result (ok, timeout, error); sem os argumentos.
call.metricsA cada turno.As latências em milissegundos: stt_ms, llm_ms (até a primeira palavra), tts_ms e end_to_end_ms.
call.supervisionUm supervisor entra, muda de modo, sussurra ou sai.action, monitor_id e mode.
call.agent.errorUm provedor falhou no meio da chamada.service, provider, status, category e action (veja Quando um provedor falha).
call.agent.declinedO agente não pôde ficar com uma chamada que tocava.reason, detail, mode e outcome (veja Quem atende a chamada).
call.agent.endedO fim.reason, duration_seconds, os segundos da IA (ai_seconds) e os de um supervisor com a chamada (supervised_seconds), a transcrição completa (transcript), as ferramentas usadas (tools) e o uso (usage).

timestamp é o instante do evento em segundos, e timestamp_ms, o mesmo instante em milissegundos: use-o para ordenar eventos do mesmo segundo.

event_id é o mesmo em cada tentativa: use-o para não processar um evento duas vezes. As chamadas atendidas pelo agente e os segundos da IA entram no uso (agent_calls e agent_seconds em GET /v1/account/usage).

O reason de call.agent.ended: remote_ended (quem ligou desligou), agent ou tool (o agente encerrou), handoff_timeout, max_duration, max_call_duration, idle, media_closed, connect_timeout, accept_failed, provider_error (um provedor falhou), agent_removed, number_removed e account_blocked (o agente ou o número foi removido, ou a conta bloqueada: a chamada termina na hora), runtime_lost (o motor de agentes caiu no meio da chamada: a Voza encerra a chamada e manda este evento, sem a transcrição) ou shutdown.

Supervisão

Quem pode supervisionar é decidido pelo seu produto; a Voza só confere a chave de API. Encerrar a chamada continua sendo a ação terminate.

POST /v1/numbers/{id}/calls/{call_id}/monitor

Um token curto (mon_…, 5 minutos, uma vez) para o navegador do supervisor, com os ice_servers. O navegador envia a oferta SDP dele para POST /v1/monitor/session com Authorization: Bearer mon_… e recebe a resposta do motor e o monitor_id. mode é listen (ouve; o padrão) ou takeover (já entra falando). Esse endereço aceita páginas de qualquer domínio (CORS).

{ "session": { "sdp_type": "offer", "sdp": "v=0\r\no=- …" }, "mode": "listen" }
{ "monitor_id": "mon_1", "call_id": "wacid.ABGG…", "session": { "sdp_type": "answer", "sdp": "v=0\r\no=- …" } }
EndereçoCorpoO que faz
POST …/calls/{call_id}/whisper{"text": "Ofereça a sexta às 9h30."}O texto entra no contexto da IA; quem ligou não ouve.
POST …/calls/{call_id}/takeover{"monitor": "mon_1"}A IA para de falar e o microfone daquele supervisor vai para a chamada.
POST …/calls/{call_id}/release{}Devolve a chamada à IA.
DELETE …/calls/{call_id}/monitor/{monitor_id}Tira um supervisor da chamada; se ele estava com ela, a IA volta a falar.

No navegador, o SDK do telefone web faz a parte do supervisor: veja Supervisionar uma chamada da IA.

Chamada de teste

POST /v1/agents/test-sessions

Conversa com um agente pelo navegador, sem WhatsApp: envie {"agent_ref", "callback_url"} e receba {id, token, ice_servers, expires_at}. O navegador envia a oferta SDP para POST /v1/agents/test-sessions/connect com Authorization: Bearer agt_… (uma vez) e recebe a resposta do motor. Com number_id, o motor assina com o segredo do agente desse número, e os eventos saem com "test": true para onde vão os do número; sem ele, a resposta traz um secret só desta chamada, e os eventos não são enviados. DELETE /v1/agents/test-sessions/{id} encerra.

Uma chamada de teste dura no máximo 10 minutos e conta nas sessões de agente da conta: acima de limits.agent_tests chamadas de teste ao mesmo tempo, connect responde 429 test_session_limit_reached (e 429 agent_limit_reached acima de limits.agent_sessions). O token pode ser usado de novo depois de uma recusa assim.

Voz e chaves de IA

PUT /v1/account/ai-keys

Guarda, encriptadas, as chaves dos provedores de IA da conta: anthropic, deepgram, elevenlabs e cartesia. Só os provedores enviados mudam, e null remove uma chave. Só uma chave de API criada por um proprietário da conta pode mudá-las (senão, 403 owner_only). Elas nunca são devolvidas: a resposta, como GET, só diz quais existem.

Antes de guardar, a Voza confere cada chave nova no próprio provedor, num endereço que não gasta nada (a lista de modelos, de projetos ou de vozes). Se ele recusar uma chave (401), nenhuma chave do pedido fica guardada, e a resposta é 422 invalid_provider_key, com os provedores em meta.providers. Um provedor fora do ar, lento, ou que aceita a chave sem a permissão daquela lista, não impede a chave.

{ "anthropic": "sk-ant-…", "deepgram": "…" }
{ "anthropic": true, "deepgram": true, "elevenlabs": false, "cartesia": false }
POST /v1/tts

{"provider", "voice_id", "text", "speed"} → o áudio (audio/mpeg, ou audio/wav com "format": "wav"), com a chave do provedor guardada na conta. Serve para ouvir a saudação antes de publicá-la. O texto não fica guardado; uma frase fixa que uma chamada do agente já guardou (a mesma voz e o mesmo texto) volta sem esperar (X-Voza-Cache: hit).

GET /v1/voices?provider=elevenlabs

As vozes da conta num provedor (elevenlabs ou cartesia), lidas com a chave guardada na conta, então as vozes clonadas vêm também. Cada voz traz id (o voice_id da configuração e de /v1/tts), name, languages, gender e accent quando o provedor diz, e preview_url. A lista fica guardada por uma hora, por conta e provedor.

{ "provider": "elevenlabs", "data": [{ "id": "21m00Tcm4TlvDq8ikWAM", "name": "Rachel", "languages": ["en", "pt-BR"], "gender": "female", "accent": "american", "preview_url": "https://…/preview.mp3" }] }

Erros dos agentes

StatusCódigoQuando acontece
401invalid_monitor_token, invalid_test_tokenO token de supervisão ou da chamada de teste está ausente, é inválido ou venceu.
403owner_onlySó uma chave de API criada por um proprietário guarda as chaves de IA.
404monitor_not_foundNão há um supervisor com esse id na chamada.
409agent_not_configuredO número não tem um agente.
409agent_unavailableO seu produto recusou a chamada (respondeu ao agent.config com 409 ou 410 e agent_unavailable).
409call_not_ringingA chamada não está mais tocando.
409call_not_with_agentA chamada não está com o agente: outra pessoa atendeu, ela terminou ou ainda não foi atendida.
409token_usedO token de supervisão ou da chamada de teste já foi usado.
409too_many_monitors, not_in_controlA chamada já tem todos os supervisores, ou outro supervisor a assumiu.
422missing_provider_keyFalta a chave de um provedor que o agente usa: guarde-a em PUT /v1/account/ai-keys.
422invalid_offerO motor não conseguiu responder à oferta SDP.
422invalid_provider_keyUm provedor recusou a chave enviada em PUT /v1/account/ai-keys.
429agent_limit_reached, test_session_limit_reachedA conta já tem todas as sessões de agente, ou chamadas de teste, simultâneas que pode ter.
502agent_config_unavailableO seu produto não enviou a configuração a tempo, e o motor não tinha uma guardada.
502tts_failedO provedor de voz não gerou o áudio.
502voices_failedO provedor de voz não devolveu a lista de vozes (uma chave recusada, por exemplo).
503agents_unavailable, agents_busyO motor de agentes não respondeu ou está no limite de chamadas. meta.reason traz o motivo que o motor deu.

Conta, plano e uso

GET /v1/account
{
  "id": 42,
  "name": "Sua Empresa",
  "status": "active",
  "trial_ends_at": null,
  "partner": false,
  "plan": "pro",
  "subscription": { "status": "active", "currency": "brl", "renews_at": "2026-11-23T14:30:00Z", "ends_at": null },
  "limits": { "numbers": 5, "channels": 10, "requests_per_minute": 600, "agent_sessions": 10, "agent_tests": 2 },
  "webhook_retry_hours": 24
}
CampoDescrição
statustrial (teste grátis), active (ativa), suspended (suspensa) ou canceled (assinatura encerrada).
planstart, pro, scale, ou vazio sem assinatura.
subscriptionA assinatura no Stripe: status (active, past_due quando um pagamento falhou, canceled…), a moeda e as datas. Vazio sem assinatura.
limitsQuantos números a conta conecta, quantas chamadas simultâneas pode ter, quantas requisições por minuto faz na API e quantas sessões de agente de voz tem ao mesmo tempo (agent_sessions, das quais até agent_tests chamadas de teste).
webhook_retry_hoursPor quantas horas um evento de mensagem que falhou é tentado de novo: 24, ou o prazo combinado com a Voza, até 168 (7 dias).
partnerUma conta parceira, de outro produto, com limites combinados com a Voza: as chamadas dela não são recusadas pelo limite de chamadas simultâneas, porque o produto limita as dos seus clientes.

A assinatura se faz no painel (Plano e uso), com cartão, pelo Stripe. Quando ela termina, a API responde subscription_inactive até a conta assinar de novo.

GET /v1/account/usage?month=2026-10
{
  "month": "2026-10",
  "totals": { "messages_sent": 1840, "messages_received": 2311, "calls": 96, "call_seconds": 20412, "agent_calls": 40, "agent_seconds": 7310 },
  "numbers": [
    { "number_id": "01jbk3…", "name": "Loja Centro", "messages_sent": 1200, "messages_received": 1500, "calls": 60, "call_seconds": 14000, "agent_calls": 40, "agent_seconds": 7310 }
  ]
}

O uso do mês em UTC, no total e por número (number_id vazio para um número já excluído). Sem month, o mês atual. agent_calls e agent_seconds são as chamadas que um agente de voz atendeu e os segundos da IA nelas (sem o tempo em que um supervisor estava com a chamada).

OpenAPI e Postman

A API inteira está descrita em OpenAPI 3.1: https://www.vozaapi.com/docs/openapi.json?lang=pt-BR. Importe-a no Postman ou no Insomnia, ou gere um cliente na sua linguagem.

Os textos da especificação vêm no idioma de ?lang=: pt-BR, en ou es.

Status da Voza

O status da Voza ao vivo (site e API, banco de dados, webhooks e e-mails, novas tentativas, os números por QR code e os agentes de voz) está em https://www.vozaapi.com/status. Para um monitor de disponibilidade, o mesmo em JSON: https://www.vozaapi.com/status.json, que responde 503 quando algum serviço está fora ou atrasado.

{
  "status": "operational",
  "checked_at": "2026-10-27T15:00:00Z",
  "components": [{ "key": "engine", "name": "Números por QR code", "status": "operational", "message": null }]
}

status é operational, degraded ou down. Os números oficiais dependem também da API da Meta, que esta página não acompanha.

Novidades

Mudanças que possam quebrar a sua integração são avisadas com antecedência e entram numa nova versão do endereço (/v2); a /v1 só ganha campos e eventos novos.