Operação
Números e pareamento
O número é a linha por onde se fala, e o canal é propriedade dele. Você usa o
numberId para enviar, e o estado de conexão para saber se aquela
linha está de pé.
Listar os seus números
GET /v1/numbers
curl -H "x-api-key: $NUNTIS_API_KEY" \
https://api.nuntis.com.br/v1/numbers
{
"data": [
{
"id": "num_4711",
"channel": "whatsapp-unofficial",
"label": "Comercial",
"phoneE164": "+5511999990000",
"active": true,
"sendingEnabled": true,
"status": "connected",
"since": "2026-08-13T13:30:00.000Z",
"sinceUnix": 1786533000,
"lastCheckedAt": "2026-08-13T13:44:30.000Z",
"lastCheckedAtUnix": 1786534070
},
{
"id": "num_4712",
"channel": "telegram",
"label": "Bot de atendimento",
"phoneE164": null,
"active": true,
"sendingEnabled": false,
"status": "connected",
"since": null,
"sinceUnix": null,
"lastCheckedAt": null,
"lastCheckedAtUnix": null
}
]
}
A resposta traz todos os seus números e nunca é truncada em silêncio — não há paginação nesta rota. Se um dia houver, ela entra de forma aditiva, sem mudar o que já existe.
Se você ainda não tem número nenhum, a resposta é 200 com
data: [] — nunca 404.
A lista traz também os números desativados, com
active: false — é o que permite você ver o que desativou.
Cada item traz ainda sendingEnabled: se aquele número pode
enviar agora. É uma pergunta diferente de active e
de status, e é a que você precisa antes de enfileirar —
veja abaixo.
active não é status
active é a sua decisão sobre o cadastro
(ligado ou desligado); status é o fato observado do canal
(conectado ou não). Um número ativo pode estar desconectado, e um
recém-desativado pode aparecer conectado por alguns segundos até o estado
convergir.
Cadastrar e desativar número é feito por você, pelo contrato — veja Provisionamento. Esta rota só lê, e só o que é seu.
Pode enviar? sendingEnabled
active e status não respondem à pergunta que importa
na hora de enviar. Um número pode estar ativo, conectado — e mesmo assim não
enviar. Quem responde isso é sendingEnabled, presente em
GET /v1/numbers e em GET /v1/numbers/{id}/status.
| Campo | Responde | Quem decide |
|---|---|---|
active | O cadastro está ligado? | Você, por DELETE /v1/numbers/{id}. |
status | O canal está conectado? | O fato observado — o telefone, a rede, o canal. |
sendingEnabled | Este número pode enviar agora? | A operação da plataforma Nuntis. Não há rota para você — nem para ligar, nem para desligar. |
O ciclo de vida de um número
cadastrado active: true status: disconnected sendingEnabled: true
pareado active: true status: connected sendingEnabled: true
(desligado) active: true status: connected sendingEnabled: false
O número já nasce podendo enviar — cadastrar e parear não deixam nenhuma liberação pendente. O que falta, entre o cadastro e a primeira mensagem, é o pareamento: sem sessão conectada não há por onde sair.
A terceira linha não é uma etapa do ciclo, e sim um estado que pode aparecer a qualquer momento: a operação do Nuntis desliga o envio de um número para proteger a linha, e há também o desligamento geral do serviço, que zera o envio de todos de uma vez. Não existe ordem garantida aqui — por isso o campo se lê, não se deduz do ponto do fluxo em que você está. Veja Provisionamento para o cadastro.
sendingEnabled: false não dá erro
A mensagem é aceita com 202 e fica em queued por
tempo indeterminado — sem falha, sem recusa, sem aviso. Quando o envio
voltar, tudo o que ficou retido sai de uma vez. Uma
mensagem escrita há três dias chegando ao lead como se fosse nova é falha
visível do seu produto, não do Nuntis.
Leia o campo antes de enfileirar e bloqueie a ação na sua interface enquanto
ele for false.
Guardar uma cópia local (“nasceu ligado, marco a flag aqui”) começa correta e fica errada silenciosamente: desligar e religar o envio acontece do lado do Nuntis, e o seu banco não fica sabendo. Esta rota é a fonte de verdade — consulte-a.
Na dúvida o Nuntis responde false. O campo nunca diz
true por omissão: se a informação faltar, a resposta é a
conservadora.
Estados de conexão
| Estado | Significa |
|---|---|
connected | Transporte ligado e sessão pareada. Pronto para enviar. |
disconnected | Transporte desligado. |
qr_required | Transporte ligado, sessão não pareada. Precisa escanear o código. |
connecting | Conexão pedida, ainda sem um estado conclusivo. |
unreachable | O canal parou de responder. É veredito do Nuntis, não do canal. |
banned | O número foi bloqueado pelo canal. |
disconnected
O conjunto é aditivo: valores novos podem aparecer. Um
switch exaustivo que hoje compila vai encontrar um valor que
não conhece amanhã, em produção. O default seguro é
disconnected — ele leva você a verificar, em vez de assumir que
está tudo bem. A mesma regra vale para channel.
Não há ordem entre os estados
Ao contrário do status de mensagem, aqui não existe escada. Um número vai e volta livremente entre conectado, desconectado e pareamento pendente — um telefone sai do ar e volta, e um “estado que só avança” faria o contrato mentir sobre a realidade física.
Consultar o estado de um número
GET /v1/numbers/{id}/status
{
"status": "connected",
"since": "2026-08-13T13:30:00.000Z",
"sinceUnix": 1786533000,
"lastCheckedAt": "2026-08-13T13:44:30.000Z",
"lastCheckedAtUnix": 1786534070,
"reason": null,
"sendingEnabled": true
}
O reason é sempre presente e null na esmagadora
maioria das transições — queda de canal, encerramento pedido por você e
pareamento normal não têm motivo declarado. Quando ele vem preenchido, é
porque o telefone que pareou não bate com o que você cadastrou:
os dois motivos e o que fazer.
lastCheckedAt
A resposta vem do estado que o Nuntis mantém, por
verificação periódica — não de uma consulta ao canal feita agora.
lastCheckedAt é a idade da informação, e um valor
antigo significa que o próprio canal pode estar inalcançável.
disconnected com lastCheckedAt: null quer dizer
“nunca verifiquei”, que é diferente de “verifiquei e está fora”.
Você não precisa consultar em laço: mudanças de estado chegam sozinhas pelo
evento connection.updated, e
o numberId de lá é o mesmo dos eventos de mensagem.
Parear um número
POST /v1/numbers/{id}/pairing
curl -X POST -H "x-api-key: $NUNTIS_API_KEY" \
https://api.nuntis.com.br/v1/numbers/num_4711/pairing
{
"status": "qr_required",
"qr": {
"code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"format": "png-data-uri",
"obtainedAt": "2026-08-13T13:45:12.000Z",
"obtainedAtUnix": 1786534112
},
"hint": { "refreshAfterSeconds": 20 }
}
O qr.code é um data URI completo, pronto para usar
direto:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." alt="Código de pareamento">
Chame de novo para obter um código fresco enquanto o operador não escaneia —
use o hint.refreshAfterSeconds como intervalo. Não existe
“sessão de pareamento” para você manter do seu lado, e não há nada para
encerrar se o operador desistir.
Se o número já estiver conectado, a resposta é
200 com status: "connected" e qr: null.
Pedir pareamento do que já está pareado não é erro.
| Resposta | Quando |
|---|---|
404 NOT_FOUND | Número inexistente — ou de outro produto. |
422 CHANNEL_CAPABILITY_UNSUPPORTED | O canal não usa pareamento por código. |
422 NUMBER_DEACTIVATED | O número tem active: false. Reativar não é self-service. |
503 CHANNEL_UNREACHABLE | Canal indisponível. retryable: true. |
504 PAIRING_QR_TIMEOUT | O canal não produziu um código a tempo. retryable: true. |
Fluxo de pareamento
- Chame
POST /v1/numbers/{id}/pairing. - Se vier
status: "connected", acabou. - Exiba o
qr.codeao operador. - Repita a chamada a cada ~20 s e substitua a imagem, enquanto ninguém escanear.
- Pare quando chegar
connection.updatedcomstate: "connected", ou quando a chamada devolverconnected.
Encerrar a sessão
DELETE /v1/numbers/{id}/session
curl -X DELETE -H "x-api-key: $NUNTIS_API_KEY" \
https://api.nuntis.com.br/v1/numbers/num_4711/session
Destrói a sessão pareada: o número volta a exigir pareamento. É idempotente — sessão já encerrada devolve o mesmo resultado.
O número continua cadastrado e ativo: o que morre é a sessão.
DELETE /v1/numbers/{id}
Sem o /session no final, a chamada desativa o
cadastro — o número para de parear, de sinalizar presença e de
enviar, e reativar não é self-service. Veja
Desativar.
Sinalizar presença
POST /v1/numbers/{id}/presence
curl -X POST https://api.nuntis.com.br/v1/numbers/num_4711/presence \
-H "x-api-key: $NUNTIS_API_KEY" \
-H "content-type: application/json" \
-d '{ "conversationId": "conv_991", "state": "typing" }'
Estados aceitos: typing, recording,
paused e seen. O último marca como lidas as
mensagens recebidas daquela conversa. A resposta é 204, sem
corpo.
Ao contrário do envio de mensagem, presença vale agora ou não vale.
“Digitando…” entregue 40 segundos depois não é atraso, é mentira — o
operador já parou de digitar. Por isso não há garantia de entrega, não há
registro no histórico da conversa, e a proteção contra abuso é um teto por
minuto que devolve 429 RATE_LIMITED.
Capacidade do canal
GET /v1/channels
Consulte uma vez, no boot da sua aplicação, e guarde. A declaração diz o que
cada canal sabe fazer — e é a resposta certa para quase toda pergunta que
começa com “mas neste canal…”. O id daqui é o mesmo valor que
aparece em channel na
listagem de números: é por ele que você liga um número à
sua capacidade.
A resposta é completa — cada canal traz todos os campos, sem omissão. Abaixo ela está transcrita inteira, como está no ar hoje:
{
"data": [
{
"id": "whatsapp-unofficial",
"texto": true,
"midia": ["image", "audio", "video", "document"],
"midiaRecebimento": ["image", "audio", "video", "document", "sticker"],
"reply": true,
"botoes": false,
"audioPtt": true,
"deliveryReceipts": "read",
"pairing": "qr",
"janelaResposta": { "exige": false },
"template": { "exige": false },
"presence": true,
"presenceStates": ["typing", "recording", "paused", "seen"],
"grupos": false,
"echoDeSaida": true,
"idDeMensagemReservavel": true,
"avatar": true,
"provisioning": "self-service"
},
{
"id": "telegram",
"texto": true,
"midia": ["image", "audio", "video", "document"],
"midiaRecebimento": ["image", "audio", "video", "document", "sticker"],
"reply": true,
"botoes": false,
"audioPtt": true,
"deliveryReceipts": "none",
"pairing": "none",
"janelaResposta": { "exige": true },
"template": { "exige": false },
"presence": true,
"presenceStates": ["typing", "recording"],
"grupos": false,
"echoDeSaida": false,
"idDeMensagemReservavel": false,
"avatar": false,
"provisioning": "managed"
}
]
}
Os valores acima são o retrato de hoje, não uma promessa: eles mudam quando um canal ganha capacidade, e canal novo entra de forma aditiva. Por isso a regra é sempre a mesma — leia o campo, não deduza pelo nome do canal.
O que cada campo decide na sua interface
| Campo | O que decide na sua interface |
|---|---|
id | Identificador do canal. Cruze com channel do número para saber qual declaração vale para ele. |
texto | Se aceita mensagem de texto. Todo canal aceita — é true sempre. |
midia | Tipos de mídia aceitos no envio. Tipo fora da lista é 422 CHANNEL_CAPABILITY_UNSUPPORTED. Este campo não descreve o que o canal recebe. |
midiaRecebimento | Tipos de mídia que o canal sabe receber e entregar a você. É deliberadamente mais largo que midia: sticker chega e não é enviável por rota nenhuma. Use midia para o clipe de anexo e este para o que a sua interface precisa exibir. |
reply | Se aceita responder citando outra mensagem. |
botoes | Se aceita botões interativos. Hoje false em todos os canais — não construa a tela contando com isso. |
audioPtt | Se aceita áudio como nota de voz (PTT), em vez de arquivo anexado. |
deliveryReceipts |
none · delivered · read — até onde vão as confirmações. Com none, sent é o estado final de sucesso: delivered e read nunca chegam, e um laço esperando por eles não termina. |
pairing |
qr · none — como o número ganha sessão. Com qr, use POST /v1/numbers/{id}/pairing e mostre o código. Com none não há o que parear (a credencial é configurada conosco) e pedir pareamento devolve 422. |
janelaResposta |
Se o canal exige que o contato tenha falado primeiro — e por quanto tempo esse consentimento vale. Veja a ressalva logo abaixo: exige sem horas não é o mesmo que exige com horas. |
template | Se o canal exige template pré-aprovado para iniciar conversa. |
presence | Se aceita sinalização de presença (“digitando…”). |
presenceStates |
Quais estados de presença este canal aceita. A lista varia por canal — mandar um estado que não está nela é 422. Vazia quando presence é false. |
grupos |
Se o canal entrega conversa de grupo: a mensagem chega, a thread aparece em GET /v1/messages e o recurso leva o bloco group. Não diz nada sobre enviar — para isso veja gruposEnvio. |
gruposEnvio |
Se o canal aceita enviar para grupo. Com true, use a mesma rota POST /v1/messages com o conversationId da conversa de grupo — não há rota nem payload próprios. Com false, essa chamada responde 422 CHANNEL_CAPABILITY_UNSUPPORTED. Consulte por canal: os dois campos são eixos separados e não têm o mesmo valor em todos. |
gruposParticipantes |
Se o canal entrega a lista de participantes de uma conversa de grupo (GET /v1/conversations/{id}/participants). Com false, essa rota responde 422 CHANNEL_CAPABILITY_UNSUPPORTED. É um terceiro eixo, independente de grupos e gruposEnvio. |
echoDeSaida |
Se o canal devolve um eco do que saiu por ele. Com false, o evento message.echo nunca chega naquele canal. |
idDeMensagemReservavel |
Se o canal aceita um id de mensagem escolhido pelo Nuntis antes do envio. Junto com echoDeSaida, é o que decide se um DISPATCH_AMBIGUOUS tem reconciliação — veja a ressalva abaixo. |
avatar |
Se o canal sabe entregar a foto de perfil de um contato. Com false, aquela rota responde 422 — não reserve o espaço da foto na interface. ⚠️ true não promete que a foto existe: promete que o canal sabe responder a pergunta. |
provisioning |
self-service · managed — se você mesmo pode cadastrar um número deste canal. Valor desconhecido: trate como managed. |
janelaResposta: com horas e sem horas são regras diferentes
{ "exige": false } — o canal não exige nada. Você inicia conversa
com quem quiser.
{ "exige": true, "horas": 24 } — há uma janela que
expira: só dá para enviar dentro de N horas depois da última
mensagem recebida naquela conversa. Passou, o envio é recusado com
422 OUTSIDE_MESSAGING_WINDOW, e o remédio é template.
{ "exige": true } sem horas — não é
janela, é consentimento sem prazo: o canal não deixa iniciar
conversa com quem nunca falou com você, e é só isso. Enquanto não houver
nenhuma mensagem recebida, o envio é recusado com
422 CONVERSATION_NOT_INITIATED; assim que a pessoa mandar a
primeira mensagem, o envio passa a ser aceito — e continua
aceito, sem contagem regressiva. Não trate esse caso como “janela de 24h”:
você recusaria por conta própria um envio que o canal aceita.
DISPATCH_AMBIGUOUS tem volta
Quando o Nuntis não obtém confirmação do canal, a mensagem
pode ter saído. Onde echoDeSaida e
idDeMensagemReservavel são true, ela reaparece
sozinha na conversa e o estado se resolve.
Onde os dois são false, não há reconciliação
possível: retryable: false é a palavra final, e reenviar por
conta própria pode duplicar a mensagem no aparelho de quem recebe. Ler esses
dois campos é o que separa um retry seguro de uma mensagem duplicada — veja
Erros.
Canal novo aparece aqui de forma aditiva. Se você lê a declaração em vez de assumir, o seu código já nasce pronto para ele.