Nuntis documentação Referência da API

Começar

Autenticação

Toda requisição ao contrato /v1 leva a sua API key no header x-api-key. Não há login, não há token de sessão, não há refresh.

header
x-api-key: <sua-api-key>

Exemplo

bash
curl https://api.nuntis.com.br/v1/channels \
  -H "x-api-key: $NUNTIS_API_KEY"

Credencial em query string é recusada

Enviar a key como parâmetro de URL (?api_key=…) não funciona, e a recusa é por desenho. Query string vaza em três lugares que ninguém controla:

  • log de proxy e de servidor, onde a URL completa costuma ser gravada em texto puro;
  • histórico do navegador, se a URL passar por um;
  • o header Referer, que carrega a URL de origem para terceiros.

Header não vai para nenhum dos três por padrão. É a única forma aceita aqui.

O que a key delimita

A key é o limite. Ela identifica o seu produto, e tudo que você lê ou escreve pertence a ele — clientes, operadores, números, conversas, mensagens, subscriptions de webhook.

Recurso de outro produto responde 404

Pedir um recurso que não é seu devolve 404 NOT_FOUND, exatamente igual a pedir um recurso que não existe. Os dois casos são indistinguíveis de propósito: um 403 confirmaria a existência do recurso alheio, e com identificadores adivinháveis isso viraria um enumerador da base de outro produto.

Orçamento de requisições

Cada API key tem um orçamento em dois horizontes: por minuto e por hora. Toda resposta autenticada — inclusive as de erro — traz o estado corrente dos dois, para que você não precise adivinhar o ritmo.

HeaderO que é
X-RateLimit-Limit-MinuteTeto de requisições na janela de um minuto.
X-RateLimit-Remaining-MinuteQuantas ainda cabem nesta janela de minuto.
X-RateLimit-Reset-MinuteInstante (ISO 8601) em que a janela de minuto vira.
X-RateLimit-Limit-HourTeto na janela de uma hora.
X-RateLimit-Remaining-HourQuantas ainda cabem nesta janela de hora.
X-RateLimit-Reset-HourInstante em que a janela de hora vira.
bash
curl -sD - -o /dev/null \
  -H "x-api-key: $NUNTIS_API_KEY" \
  https://api.nuntis.com.br/v1/channels

# x-ratelimit-limit-minute: 60
# x-ratelimit-remaining-minute: 59
# x-ratelimit-reset-minute: 2026-09-02T16:21:00.000Z
# x-ratelimit-limit-hour: 1000
# x-ratelimit-remaining-hour: 999
# x-ratelimit-reset-hour: 2026-09-02T17:00:00.000Z

A janela é de calendário, não deslizante

Reset é um instante, não um número de segundos. As janelas viram no relógio: a de minuto no segundo :00, a de hora no minuto :00. Consequência prática: o orçamento não se recompõe aos poucos — ele volta inteiro na virada.

Quem supõe janela deslizante programa um backoff que vai devolvendo cota aos poucos, continua batendo no limite e conclui que “o rate limit está errado”. O comportamento correto é esperar até o Reset.

Quando estoura

A resposta é 429 RATE_LIMITED, e ela traz o trio sem sufixo — referente à janela que estourou, que pode ser a de minuto ou a de hora — mais Retry-After em segundos:

http
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-09-02T16:21:00.000Z
Retry-After: 42

RATE_LIMITED é retryable: true. Respeite Retry-After — retentar antes só gasta orçamento da janela seguinte.

O header é um espelho, não uma garantia

Se o nosso contador de requisições ficar indisponível, a requisição passa e Remaining reporta o orçamento cheio — preferimos deixar passar a recusar tráfego legítimo por um problema nosso.

Isso significa que Remaining alto não promete que a próxima chamada será aceita. Trate 429 como possível a qualquer momento, e faça do Retry-After — e não do Remaining — a sua fonte de verdade quando ele chegar.

Os contadores são por key

Cada API key tem o próprio orçamento. Uma key gastando a cota dela não afeta outra, e os números que você vê são sempre os da key que fez a chamada.

Escopos

Além do produto, a key carrega escopos: o conjunto de coisas que ela pode fazer. Eles são definidos na emissão — peça os que você precisa, e só eles.

EscopoO que libera
messages:send POST /v1/messages
messages:read GET /v1/messages, GET /v1/messages/{id} e a recuperação de mídia (POST /v1/messages/{id}/media/redownload)
numbers:read GET /v1/numbers, GET /v1/numbers/{id}/status e GET /v1/channels
numbers:session POST /v1/numbers/{id}/pairing, POST /v1/numbers/{id}/presence e DELETE /v1/numbers/{id}/session
webhooks:manage POST /v1/webhooks, GET /v1/webhooks e DELETE /v1/webhooks/{id}
customers:read GET /v1/customers e GET /v1/customers/{id}
customers:write POST /v1/customers e DELETE /v1/customers/{id}
operators:read GET /v1/customers/{id}/operators e GET /v1/operators/{id}
operators:write POST /v1/customers/{id}/operators e DELETE /v1/operators/{id}
numbers:provision POST /v1/operators/{id}/numbers e DELETE /v1/numbers/{id}
contacts:read Reservado. Nenhuma rota desta versão o utiliza.
As rotas de provisionamento exigem escopo explícito

Tudo que está em Provisionamento — criar e desativar clientes, operadores e números — só responde a uma key que tenha o escopo correspondente. Sem ele a resposta é 401 UNAUTHORIZED, mesmo com uma key perfeitamente válida para enviar mensagens.

A escolha é deliberada: capacidade nova não é concedida por omissão. Se você passou a precisar de provisionamento, peça uma key com os escopos novos — não é algo que apareça sozinho numa key antiga.

Os dois DELETE de número são separados

numbers:session permite encerrar a sessão (DELETE /v1/numbers/{id}/session), e só. Desativar o número (DELETE /v1/numbers/{id}) exige numbers:provision. Uma key de operação diária não desativa cadastro por acidente.

Uma key por produto, por ambiente

Não emita uma key por cliente final nem por operador. A separação entre os seus clientes é feita pelo modelo de recursos, não por credenciais — multiplicar keys só multiplica o que você precisa rotacionar quando uma vazar.

Guarde a key como segredo

  • Só no servidor. Nunca em aplicativo móvel, front-end web, repositório ou build de cliente — qualquer coisa que rode na máquina do usuário final é pública, mesmo compilada.
  • Em variável de ambiente (ou no cofre que você já usa), lida em tempo de execução. A key precisa ser trocável sem recompilar nada.
  • Fora dos logs. Se o seu cliente HTTP registra headers ao depurar, filtre x-api-key explicitamente.
javascript
// A key é lida do ambiente, nunca escrita no código.
const NUNTIS_URL = process.env.NUNTIS_URL;      // https://api.nuntis.com.br
const NUNTIS_API_KEY = process.env.NUNTIS_API_KEY;

async function nuntis(caminho, opcoes = {}) {
  const resposta = await fetch(`${NUNTIS_URL}/v1${caminho}`, {
    ...opcoes,
    headers: {
      'x-api-key': NUNTIS_API_KEY,
      'content-type': 'application/json',
      ...(opcoes.headers || {}),
    },
  });

  if (!resposta.ok) {
    // O corpo de erro é sempre { code, message, retryable, source }.
    const erro = await resposta.json();
    throw Object.assign(new Error(erro.message), erro);
  }

  return resposta.status === 204 ? null : resposta.json();
}

Rotação

Trocar a key é uma mudança de variável de ambiente, não de código. Se você suspeitar de vazamento, peça uma key nova e descarte a antiga — como toda chamada é autenticada por header, não há sessão pendurada para expirar.

Erros de autenticação

codeHTTPQuando acontece
UNAUTHORIZED 401 Key ausente, inválida, expirada, sem permissão para a rota, ou enviada por um meio não aceito (por exemplo, query string).
NOT_FOUND 404 A key é válida, mas o recurso pedido não é do seu produto — ou não existe. Os dois são indistinguíveis.
RATE_LIMITED 429 Limite de requisições da key excedido. Respeite o header Retry-After. Ver Orçamento de requisições.

A forma completa do corpo de erro e a lista inteira de códigos estão em Erros.