API v2
REST em JSON sob /v2, mais o WebSocket /v2/signaling que os SDKs usam. Tudo é do parceiro da credencial: o Voice nunca aceita o parceiro vindo do corpo. Chamada inexistente, de outro parceiro, de outro site ou sem participação dá o mesmo 404 call_not_found. Referência completa: OpenAPI 3.1.
Modelo
| Conceito | No Voice |
|---|---|
| Condomínio | site: ext_id seu, cadastrado com PUT /v2/sites/{site} |
| Guarita, portaria | posto (station) do site, com rótulo de até 32 caracteres |
| Morador, porteiro | ext_id seu no sub do token de aparelho; o Voice não guarda nome |
| Unidade, grupo | target da chamada: unit, group ou users |
Rotas
| Rota | Quem | O que faz |
|---|---|---|
PUT /v2/sites/{site} | servidor sites:write | cria ou atualiza enabled, max_concurrent (1 a 500), ring_timeout_s (15 a 60, padrão 30), retention_days (30 a 365), busy_policy, authorization (prior ou callback) |
DELETE /v2/sites/{site} | servidor sites:write | desliga; chamadas em curso terminam com revoked |
PUT /v2/sites/{site}/stations/{station} | servidor sites:write | {"label"} |
POST /v2/calls | servidor calls:create ou aparelho call:place (site callback) | cria; Idempotency-Key obrigatório |
GET /v2/calls/{call_id} | servidor calls:read ou participante | a chamada, com os campos do seu papel |
GET /v2/calls | servidor calls:read ou participante | ?site=&since=&cursor=&limit= (até 100); {"items", "next_cursor"} |
POST /v2/calls/{call_id}/accept, /reject | convidado | {"device_id"}; o primeiro aceite vence: 409 taken_elsewhere |
POST /v2/calls/{call_id}/ring-ack | convidado | {"device_id", "result"}: o push tocou de verdade |
POST /v2/calls/{call_id}/cancel | servidor calls:end ou origem | só antes do aceite |
POST /v2/calls/{call_id}/hangup | servidor calls:end, origem ou quem atendeu | idempotente |
POST /v2/calls/{call_id}/disposition | servidor calls:end ou quem atendeu | {"code"}: allowed, denied, no_response. Informação: nunca abre portão |
POST /v2/calls/{call_id}/device-token | aparelho convidado, com prova | token curto da chamada (autenticação) |
POST /v2/devices | aparelho call:receive | registra o aparelho (installation_id, platform, push_type, push_token); devolve device_secret; Idempotency-Key obrigatório |
DELETE /v2/devices/{device_id} | dono do aparelho | revoga o aparelho e a credencial |
DELETE /v2/users/{user}/devices | servidor devices:revoke | revoga tudo da pessoa e encerra as chamadas dela |
POST /v2/ice-credentials | participante | STUN e TURN temporários da chamada |
POST /v2/ws-ticket | participante | ticket de uso único (30 s) para abrir o /v2/signaling |
Criar a chamada
Dois modos, escolhidos por site (authorization) ou pelo padrão da conta:
| Modo | Quem cria | Quem decide quem toca |
|---|---|---|
prior (padrão) | o seu backend | o seu backend, em callees (obrigatório, até 20) |
callback | o aparelho (call:place) | o Voice pergunta ao seu authorize_url, assinado, em até 3 s |
POST /v2/calls
Authorization: Bearer <asserção com calls:create>
Idempotency-Key: pedido-123
{
"site": "cond_jardins",
"target": {"kind": "unit", "ref": "A-304"},
"callees": ["res_77", "res_78"],
"caller": {"user": "port_12", "station": "guarita_1"},
"context": {"reason": "delivery", "reason_label": "Mercado"},
"labels": {"target": "Apto 304"},
"metadata": {"pedido": "ped_123"},
"ring_timeout_s": 30
}
caller.useré o porteiro que vai entrar na chamada: osubdo token da aba dele tem de ser igual. Ele entra comattachOutgoingem até 10 s (senãoorigin_gone).context.reason:delivery,visitor,serviceouother;reason_labelaté 24 caracteres, mostrado no aparelho e apagado no fim.labelssó a origem vê;metadata(até 16 chaves, 1 KB) só volta para o seu servidor e no webhook: nunca vai a aparelho.- Campo desconhecido:
422 validation_failed.
O que pode sair
Na ordem em que o Voice confere: conta suspensa (403 partner_disabled); site desligado (404 site_not_found); alvo ocupado com one_per_target (busy/target_busy); teto do site ou da conta (failed/site_limit ou partner_limit); callback negado (403 not_allowed); ninguém alcançável (failed/no_devices); grupo com todos ocupados (busy/attendants_busy); senão ringing. Antes de tudo, a cota por posto: 30 chamadas em 10 minutos (429 rate_limited, detail: station_quota).
A chamada (CallView)
id, status, end_reason, created_at, answered_at, ended_at, ring_expires_at, role, site, target, sequence, disposition, context, e por papel:
| Campo | servidor | origem | convidado e quem atendeu |
|---|---|---|---|
devices_ringing | sim | sim, menos em group | não |
caller.user, caller.station | sim | sim | não |
answered_by | depois do aceite | depois do aceite | não |
labels | não | sim | não |
metadata | sim | não | não |
caller.station_label | não | não | sim |
my_invitation_status | não | não | sim |
Estados
created → ringing → connecting (aceita) → active (áudio conectado). Terminais: completed, missed, cancelled, rejected, failed, busy. end_reason: normal, no_answer, origin_cancelled, origin_gone, all_rejected, no_devices, quiet_hours, site_limit, partner_limit, target_busy, attendants_busy, ice_timeout, media_lost, signaling_lost, max_duration, revoked. Prazos: toque até o ring_timeout_s do site, conexão de mídia em 15 s, duração máxima de 1 h.
Idempotência
Idempotency-Key (8 a 64 de [A-Za-z0-9_.:-]) é obrigatório em POST /v2/calls e POST /v2/devices; sem ele, 400 bad_request. Guardado por 24 h, ligado a quem pediu, à rota e ao corpo: o mesmo pedido devolve a resposta guardada com 200; corpo diferente com a mesma chave, 422 idempotency_conflict; o primeiro ainda em curso, 409 idempotency_in_progress. Use o id do pedido do seu sistema.
WebSocket /v2/signaling
Os SDKs fazem isto por você; está aqui para quem escreve o próprio cliente.
- Peça o ticket em
POST /v2/ws-tickete abra o socket sem nada na URL. Primeira mensagem, em até 5 s:{"type": "auth", "ticket", "v": 2, "client": "meuapp/1.0"}. Resposta:{"type": "auth.ok", "session_id", "v": 2, "capabilities"}. Falha: fecha com4401. - O servidor manda
{"type": "ping"}a cada 20 s; responda{"type": "pong"}na hora. Sem frame por 45 s, o socket cai. - Todo frame do cliente tem
msg_ide recebeack; reenvie o mesmomsg_idse o ack não vier em 3 s. - Eventos com
event_id(deduplique) esequencepor chamada (descarte o menor). Depois de reconectar, mandecall.resumecom a últimasequence. - A offer sai só da origem e só depois do aceite; trickle ICE; quem fez a offer reinicia o ICE depois de 4 s em
disconnected. - Fechamentos:
4401(credencial; renove o token),4403(origem recusada ou conta suspensa; não insista),4408(silêncio),4429(mais de 3 sockets por aparelho).
A suíte de conformidade (sdk/conformidade) descreve cada um desses fluxos frame a frame, e todo SDK oficial roda os mesmos cenários.