Nuntis documentação Referência da API

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

bash
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" }'
201 Created
{
  "id": "cus_4471",
  "name": "Clínica Sorriso",
  "externalRef": "clinica-4471",
  "active": true,
  "createdAt": "2026-08-13T18:12:03.000Z"
}
CampoObrigatórioRegra
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

bash
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" }'
201 Created
{
  "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.

Criar operador não cadastra número

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.

bash
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"
      }'
201 Created
{
  "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.

CampoObrigatórioRegra
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.

O número nasce sem enviar

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.

Se o canal falhar, nada é aplicado

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:

provisioningO 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

bash
curl -X POST -H "x-api-key: $NUNTIS_API_KEY" \
  https://api.nuntis.com.br/v1/numbers/num_4711/pairing
200 OK
{
  "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.

200 OK — GET /v1/numbers/num_4711/status
{
  "status": "disconnected",
  "since": "2026-08-13T18:20:00.000Z",
  "sinceUnix": 1786731600,
  "lastCheckedAt": "2026-08-13T18:20:00.000Z",
  "lastCheckedAtUnix": 1786731600,
  "reason": "phone_mismatch"
}
reasonSignificaO 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.
O campo é sempre presente, e a lista é aditiva

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.

Estas rotas ainda não aceitam 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

RotaDevolve
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.
200 OK — GET /v1/customers
{
  "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.

Lista vazia e 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

RotaEfeito
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.
200 OK
{ "id": "num_4711", "active": false }

Os três respondem o mesmo corpo e são idempotentes: repetir devolve exatamente isso de novo.

Desativa, nunca apaga

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, com active: 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.

EscopoLibera
customers:readListar e detalhar clientes.
customers:writeCriar e desativar cliente.
operators:readListar operadores de um cliente e detalhar operador.
operators:writeCriar e desativar operador.
numbers:provisionCadastrar 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

codeHTTPQuando 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

  1. Leia GET /v1/channels e guarde quais canais são self-service.
  2. Crie o cliente com um externalRef seu, sempre.
  3. Crie o operador sob o cliente, também com externalRef.
  4. Cadastre o número com channel e, quando o canal exigir, phoneE164.
  5. Guarde os três ids opacos como texto, sem parsear.
  6. Chame o pareamento e mostre o QR; renove a cada ~20 s.
  7. Espere connection.updated. Se vier disconnected com reason, mostre o motivo ao operador em vez de tentar de novo em laço.
  8. Para remover, desative de baixo para cima: número, operador, cliente.