Começar
Provisionamento
Você cria os seus clientes, os operadores deles e os números — e leva um número até o QR de pareamento — em quatro chamadas, sem nenhuma etapa manual do nosso lado.
O fluxo completo
POST /v1/customers → cus_4471 cliente
POST /v1/customers/cus_4471/operators → op_991 operador do cliente
POST /v1/operators/op_991/numbers → num_4711 número do operador
POST /v1/numbers/num_4711/pairing → QR o operador escaneia
Cada recurso nasce sob o anterior: o pai vem no caminho, e não no corpo. Assim não existe recurso órfão nem recurso pendurado no pai errado por um campo esquecido. O modelo inteiro é esse encaixe.
1. Criar um cliente
POST /v1/customers
curl -X POST https://api.nuntis.com.br/v1/customers \
-H "x-api-key: $NUNTIS_API_KEY" \
-H "content-type: application/json" \
-d '{ "name": "Clínica Sorriso", "externalRef": "clinica-4471" }'
{
"id": "cus_4471",
"name": "Clínica Sorriso",
"externalRef": "clinica-4471",
"active": true,
"createdAt": "2026-08-13T18:12:03.000Z"
}
| Campo | Obrigatório | Regra |
|---|---|---|
name |
sim | 1 a 120 caracteres. Livre e não único — quem correlaciona com o seu sistema é o externalRef. |
externalRef |
não | 1 a 50 caracteres, apenas letras, números, ponto, hífen e underscore. Único entre os seus clientes ativos. |
Campo desconhecido no corpo é recusado, não ignorado: a
resposta é 400 VALIDATION_FAILED. Isso é de propósito — um campo
ignorado em silêncio faz você achar que configurou algo que nunca chegou.
2. Criar um operador
POST /v1/customers/{id}/operators
curl -X POST https://api.nuntis.com.br/v1/customers/cus_4471/operators \
-H "x-api-key: $NUNTIS_API_KEY" \
-H "content-type: application/json" \
-d '{ "name": "Ana — comercial", "externalRef": "user-991" }'
{
"id": "op_991",
"name": "Ana — comercial",
"externalRef": "user-991",
"active": true,
"createdAt": "2026-08-13T18:12:03.000Z"
}
As regras de name e externalRef são as mesmas do
cliente. O recurso não publica o cliente ao qual pertence: o
vínculo você obtém por GET /v1/customers/{id}/operators. Dois
caminhos para a mesma verdade divergem no primeiro que for esquecido.
São passos separados — um operador pode ter mais de uma linha, e uma linha nova não deveria aparecer como efeito colateral de um cadastro de pessoa.
3. Cadastrar um número
POST /v1/operators/{id}/numbers
Esta chamada cria o número e prepara o canal na mesma requisição.
curl -X POST https://api.nuntis.com.br/v1/operators/op_991/numbers \
-H "x-api-key: $NUNTIS_API_KEY" \
-H "content-type: application/json" \
-d '{
"channel": "whatsapp-unofficial",
"phoneE164": "+5511999990000",
"label": "Comercial — Ana"
}'
{
"id": "num_4711",
"channel": "whatsapp-unofficial",
"label": "Comercial — Ana",
"phoneE164": "+5511999990000",
"active": true,
"status": "disconnected",
"since": "2026-08-13T18:12:03.000Z",
"sinceUnix": 1786731123,
"lastCheckedAt": "2026-08-13T18:12:03.000Z",
"lastCheckedAtUnix": 1786731123
}
O corpo do 201 é exatamente o item que
GET /v1/numbers devolve no
instante seguinte — mesmo shape, mesmos campos. Você não precisa de dois
tratamentos.
| Campo | Obrigatório | Regra |
|---|---|---|
channel |
sim | O id do canal, exatamente como aparece em GET /v1/channels. Canal desconhecido responde 400. |
phoneE164 |
depende do canal | Formato E.164 (+ e o código do país). Obrigatório nos canais cuja identidade é o telefone; ignorado nos que não têm telefone. |
label |
não | 1 a 120 caracteres, livre e não único. Ausente vira label: null — o Nuntis não inventa um rótulo a partir do telefone. |
Espaços, hifens e parênteses no telefone são aceitos e normalizados. O que não é aceito é número que não existe no plano de numeração do país.
Cadastrar N números não produz uma mensagem sequer. Habilitar o envio é um passo deliberado da operação do Nuntis, por política de proteção da linha — volume que aparece do nada no primeiro dia de um número é exatamente o padrão que o canal pune.
A resposta é 503 CHANNEL_UNREACHABLE e o número
não existe — nem parcialmente. Repita a chamada com backoff;
não há estado meio-criado para você limpar.
Quais canais aceitam cadastro
Cada canal declara isso em GET /v1/channels, no campo
provisioning:
provisioning | O que significa |
|---|---|
self-service |
Você cadastra por POST /v1/operators/{id}/numbers e o Nuntis prepara o canal na mesma requisição. |
managed |
O cadastro depende de um passo administrativo (a credencial do canal é configurada conosco). O POST responde 422 CHANNEL_CAPABILITY_UNSUPPORTED. |
O conjunto é aditivo: trate um valor que você não conhece
como managed — ou seja, como “não tente cadastrar sozinho” — em
vez de quebrar.
O telefone é único na plataforma inteira
409 PHONE_NUMBER_IN_USE
A verificação não é apenas dentro do seu produto: um telefone que já pertence a um número ativo em qualquer produto é recusado. A regra é global porque o bloqueio do canal é por telefone físico, e duas sessões disputando o mesmo aparelho é a receita conhecida de bloqueio.
A resposta não diz de quem é o telefone. Revelar o dono transformaria a checagem num oráculo que responde “este número é cliente de vocês?” para qualquer key válida.
Desativar o número que ocupa um telefone libera o valor para um cadastro novo.
4. Parear
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-13T18:13:10.000Z",
"obtainedAtUnix": 1786731190
},
"hint": { "refreshAfterSeconds": 20 }
}
O qr.code vai direto num <img src="…">. Mostre
a imagem, renove a cada ~20 s enquanto ninguém escanear, e pare quando chegar
connection.updated com
state: "connected". O passo a passo completo, com os erros
possíveis, está em
Números e pareamento.
O telefone que pareou tem que ser o que você declarou
Quando a sessão conecta, o Nuntis compara o telefone real do aparelho com o
phoneE164 que você cadastrou. Se eles não forem o mesmo, a sessão
é encerrada e o número volta para
disconnected — o cadastro nunca é sobrescrito pelo que apareceu do
outro lado.
O motivo chega no campo reason, presente nas duas superfícies:
GET /v1/numbers/{id}/status e o evento
connection.updated.
{
"status": "disconnected",
"since": "2026-08-13T18:20:00.000Z",
"sinceUnix": 1786731600,
"lastCheckedAt": "2026-08-13T18:20:00.000Z",
"lastCheckedAtUnix": 1786731600,
"reason": "phone_mismatch"
}
reason | Significa | O que fazer |
|---|---|---|
phone_mismatch |
O aparelho que escaneou não é o telefone declarado em phoneE164. |
Pareie o aparelho certo, ou corrija o cadastro (desative o número e cadastre com o telefone correto). |
phone_conflict |
O aparelho que escaneou já pertence a outro número ativo. | Desative o número que ocupa aquele telefone antes de parear este. |
null |
Sem motivo declarado — é o caso da esmagadora maioria das transições. | Nada. Queda de canal, logout pedido por você e pareamento normal não têm motivo. |
reason aparece sempre — quase sempre como null —
para que o seu cliente não quebre no dia em que um motivo novo existir. Trate
valor desconhecido como “sem motivo declarado”.
Correlacionar com os seus IDs: externalRef
Todo identificador do Nuntis é opaco (cus_4471,
op_991, num_4711). Para amarrar cada recurso ao seu
próprio registro, cliente e operador aceitam um externalRef.
- 1 a 50 caracteres, apenas
A-Z a-z 0-9 . _ -. - Único por tipo, dentro do seu produto, entre os recursos ativos. Um cliente e um operador podem ter o mesmo valor sem conflito — o id de uma pessoa e o id de uma empresa vivem em tabelas diferentes do seu lado e não têm por que competir aqui.
-
Repetir um valor em uso devolve
409 RESOURCE_ALREADY_EXISTS. A resposta não revela o recurso existente: só você sabe se foi um retry ou uma colisão de verdade. - Desativar libera o valor para reuso.
Número não tem externalRef: o correlacionador natural dele é o
próprio phoneE164, que já é único entre os ativos.
Idempotency-Key
Ao contrário do envio de mensagem,
enviar o header nos POST de provisionamento
não tem efeito, e não há dedupe algum. Um retry de rede
depois de um 201 que você não chegou a receber cria um segundo
recurso.
A proteção prática é o externalRef em cliente e operador, e o
phoneE164 em número: reenviar o mesmo valor devolve
409 em vez de duplicar. Sempre mande um — é o
que transforma um retry cego em resposta previsível.
Consultar o que existe
| Rota | Devolve |
|---|---|
GET /v1/customers |
Todos os seus clientes, inclusive os desativados (com active: false). |
GET /v1/customers/{id} |
Um cliente. |
GET /v1/customers/{id}/operators |
Os operadores daquele cliente, inclusive os desativados. |
GET /v1/operators/{id} |
Um operador. |
GET /v1/numbers |
Todos os seus números, inclusive os desativados. Ver Números. |
{
"data": [
{
"id": "cus_4471",
"name": "Clínica Sorriso",
"externalRef": "clinica-4471",
"active": true,
"createdAt": "2026-08-13T18:12:03.000Z"
}
]
}
As listagens devolvem sempre um objeto com data, nunca um array na
raiz — é o que permite acrescentar campos depois sem quebrar você. Coleção
vazia responde 200 com data: [],
nunca 404.
404 dizem coisas diferentes
GET /v1/customers/{id}/operators devolve 200 com
data: [] quando o cliente existe e não tem operador; devolve
404 quando o cliente não é seu ou não existe. A distinção
importa: vazio significa “não há”, 404 significa “não é seu”.
Desativar
| Rota | Efeito |
|---|---|
DELETE /v1/customers/{id} | Desativa o cliente. |
DELETE /v1/operators/{id} | Desativa o operador. Não toca na sessão dos números. |
DELETE /v1/numbers/{id} | Desativa o número e encerra a sessão do canal. |
{ "id": "num_4711", "active": false }
Os três respondem o mesmo corpo e são idempotentes: repetir devolve exatamente isso de novo.
O cadastro continua existindo e continua aparecendo nas listagens com
active: false — é o que permite você ver o que desativou. Não há
nenhuma rota deste contrato que destrua um cadastro ou uma conta de canal de
forma irreversível.
Reativação não é self-service. Desativou por engano, fale com o suporte: o desfazer de uma operação destrutiva não pode ser outra operação automática do mesmo ator.
Não há cascata — desative de baixo para cima
Desativar um pai que ainda tem filho ativo responde
409 RESOURCE_NOT_EMPTY:
1. DELETE /v1/numbers/num_4711 ← primeiro os números
2. DELETE /v1/operators/op_991 ← depois o operador
3. DELETE /v1/customers/cus_4471 ← por último o cliente
É deliberado. Um DELETE de cliente que derrubasse em silêncio
todas as sessões pareadas embaixo dele é exatamente a catástrofe que este
contrato recusa cometer — você desativa passo a passo e vê o que está
derrubando a cada um.
O que um número desativado faz
Número com active: false fica visível e inerte,
nunca invisível:
- continua em
GET /v1/numbers, comactive: false; - continua respondendo em
GET /v1/numbers/{id}/status; -
recusa agir — pareamento, presença e envio respondem
422 NUMBER_DEACTIVATED.
active não é status
active é a sua decisão (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.
Não confunda DELETE /v1/numbers/{id} com
DELETE /v1/numbers/{id}/session:
o segundo encerra a sessão e mantém o número ativo, para novo
pareamento.
Escopos necessários
As rotas desta página exigem escopo explícito na sua API key —
uma key sem ele responde 401 UNAUTHORIZED, mesmo sendo válida para
o resto do contrato.
| Escopo | Libera |
|---|---|
customers:read | Listar e detalhar clientes. |
customers:write | Criar e desativar cliente. |
operators:read | Listar operadores de um cliente e detalhar operador. |
operators:write | Criar e desativar operador. |
numbers:provision | Cadastrar número e desativar número. |
A tabela completa, com os escopos de envio, leitura e sessão, está em Autenticação.
Erros desta superfície
code | HTTP | Quando acontece |
|---|---|---|
VALIDATION_FAILED |
400 | Campo ausente, fora do tamanho, fora do alfabeto, desconhecido — ou canal que não existe, ou telefone ausente/inválido onde ele é exigido. |
UNAUTHORIZED |
401 | Key ausente, inválida, ou sem o escopo da rota. |
NOT_FOUND |
404 | Recurso inexistente, identificador malformado, ou recurso de outro produto — os três indistinguíveis de propósito. Em POST /v1/operators/{id}/numbers, também quando o operador do caminho está desativado. |
RESOURCE_ALREADY_EXISTS |
409 | externalRef já usado por um recurso ativo do mesmo tipo, no seu produto. |
RESOURCE_NOT_EMPTY |
409 | DELETE de um recurso que ainda tem filho ativo. |
PHONE_NUMBER_IN_USE |
409 | O telefone declarado já pertence a um número ativo da plataforma. |
CHANNEL_CAPABILITY_UNSUPPORTED |
422 | O canal não é self-service. |
NUMBER_DEACTIVATED |
422 | Pareamento, presença ou envio num número com active: false. |
QUOTA_EXCEEDED |
422 | Um teto de provisionamento configurado para o seu produto foi atingido. Desativar recursos devolve quota. |
CHANNEL_UNREACHABLE |
503 | O canal não respondeu. Nenhuma ação foi aplicada. retryable: true. |
A forma do corpo de erro e o catálogo inteiro estão em Erros.
Checklist de integração
- Leia
GET /v1/channelse guarde quais canais sãoself-service. - Crie o cliente com um
externalRefseu, sempre. - Crie o operador sob o cliente, também com
externalRef. - Cadastre o número com
channele, quando o canal exigir,phoneE164. - Guarde os três ids opacos como texto, sem parsear.
- Chame o pareamento e mostre o QR; renove a cada ~20 s.
- Espere
connection.updated. Se vierdisconnectedcomreason, mostre o motivo ao operador em vez de tentar de novo em laço. - Para remover, desative de baixo para cima: número, operador, cliente.