Pular para o conteúdo

Referência · v1

API do KontNotas

REST sobre JSON. Toda a base em /api/v1. Esta página documenta apenas endpoints que existem hoje — o que está no roteiro fica fora dela até entrar no ar.

Autenticação

Chave de API no cabeçalho Authorization, formato Bearer. Cada chave pertence a uma organização e carrega escopos — que são as mesmas permissões do produto. Uma chave sem invoice.issue pode criar rascunho, mas não emite.

Requisição
curl https://notas.kontfy.com.br/api/v1/invoices \
  -H "Authorization: Bearer kn_live_SUA_CHAVE_AQUI"

A chave é exibida uma única vez, no momento da criação. O servidor guarda apenas o hash — não há como recuperá-la depois, só gerar outra. Crie e revogue em Integrações → API.

Ambientes

O prefixo da chave define o ambiente: kn_test_ opera em sandbox, kn_live_ em produção. Uma chave de produção só funciona depois que a organização estiver efetivamente em produção — caso contrário a requisição é recusada com 422, em vez de emitir algo simulado como se fosse real.

Idempotência

Emissão de nota fiscal não pode ser repetida por acidente. Envie Idempotency-Key em todo POST que cria documento — um UUID por tentativa lógica, reutilizado nas repetições da mesma tentativa.

Comportamento
Mesma chave + mesmo corpo   → devolve a resposta original (não cria de novo)
Mesma chave + corpo diferente → 409 KONTNOTAS_IDEMPOTENCY_CONFLICT
Mesma chave, ainda em curso   → 409 KONTNOTAS_CONFLICT (aguarde e consulte)

Timeout não significa falha. Se a conexão cair antes da resposta, repita a requisição com a mesma chave — nunca com uma nova.

Os dois 409 pedem reações opostas: KONTNOTAS_CONFLICT é passageiro — a primeira requisição ainda está rodando, espere e repita —, e KONTNOTAS_IDEMPOTENCY_CONFLICT é definitivo: a chave foi reaproveitada para outro conteúdo, e repetir não muda nada.

Erros

Todo erro devolve o mesmo envelope, com código estável, motivo e a ação sugerida. O campo requestId também vai no cabeçalho X-Request-Id e é o que permite correlacionar com o log do servidor no suporte.

422 Unprocessable Entity
{
  "error": {
    "code": "KONTNOTAS_FISCAL_CONFIG_INCOMPLETE",
    "message": "A alíquota de ISS não está definida para este serviço.",
    "reason": "O serviço svc_01J... não tem issRateBps e o perfil fiscal não define padrão.",
    "action": "Defina a alíquota em Serviços ou em Configurações › Fiscal.",
    "requestId": "req_01J8XQ2K7M4N5P6Q7R8S9T0V"
  }
}

Erros de validação (422 KONTNOTAS_VALIDATION_ERROR) trazem fieldErrors, com as mensagens por campo — e o campo só existe quando há erro de campo; ele não vem como null nos outros erros. Segredos e detalhes internos nunca aparecem no envelope — eles vão para o log correlacionado.

422 — erro de campo
{
  "error": {
    "code": "KONTNOTAS_VALIDATION_ERROR",
    "message": "Revise os dados da nota.",
    "action": "Revise os campos destacados e tente novamente.",
    "fieldErrors": { "items.0.unitPriceCents": ["O valor deve estar em centavos inteiros."] },
    "requestId": "req_01J8XQ2K7M4N5P6Q7R8S9T0V"
  }
}

Limites de uso

O limite varia por plano e é aplicado por chave. Toda resposta traz os cabeçalhos RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset. Ao estourar, a resposta é 429 com Retry-After.

O limite de notas do plano é outra coisa: atingido, a emissão responde 402 KONTNOTAS_PLAN_LIMIT_REACHED. Dá para saber antes em GET /api/v1/usage.

Notas fiscais

POST/api/v1/invoicesescopo invoice.create

Cria a nota e, por padrão, já solicita a emissão.

O cliente pode ser informado por customerId ou por customer.document — integrações costumam conhecer o CNPJ, não o id interno. Passe "issue": false para deixar em rascunho. Cada item referencia o serviço por serviceId — é dele que saem código municipal, item da LC 116 e alíquota.

Corpo
{
  "customer": { "document": "11.222.333/0001-81" },
  "documentType": "NFSE",
  "description": "Consultoria de marketing — agosto/2026",
  "competenceDate": "2026-08-31",
  "items": [
    {
      "description": "Gestão de mídias sociais",
      "quantityMilli": 1000,
      "unitPriceCents": 350000,
      "unit": "UN"
    }
  ],
  "issue": true
}

Valores são inteiros em centavos e quantidades em milésimos (1 unidade = 1000). Não há ponto flutuante em lugar nenhum da API — é o que garante que R$ 0,01 nunca desapareça num arredondamento.

A resposta é 202 com a nota — inclusive com "issue": false. A emissão é assíncrona: a nota volta em queued (ou draft) e o desfecho chega depois, pela consulta ou pelos webhooks.

Serviço de construção civil (subitens 07.02, 07.04, 07.05, 07.06, 07.07, 07.08, 07.17, 07.19 e 14.14 em obra): o fisco recusa a nota sem a identificação da obra. Envie-a em obra, numa das três formas — o número do CNO, o endereço da obra ou o código CIB. O mesmo objeto volta na resposta.

Obra — uma das três formas
{ "obra": { "tipo": "cno", "codigo": "12.345.67890/12" } }

{ "obra": { "tipo": "endereco", "cep": "89010-000", "logradouro": "Rua XV de Novembro",
            "numero": "100", "bairro": "Centro" } }

{ "obra": { "tipo": "cib", "codigo": "1234567X" } }
GET/api/v1/invoicesescopo invoice.view

Lista as notas da organização, com filtros por status e cliente.

Filtros: status e customerId. Paginação por limit (1 a 100, padrão 25) e offset. Não há filtro por período — para um recorte de datas, pagine e filtre pelo competenceDate de cada nota.

Resposta
{
  "object": "list",
  "data": [ { "object": "invoice", "id": "inv_01J...", "status": "authorized", … } ],
  "total": 128,
  "hasMore": true
}
GET/api/v1/invoices/{id}escopo invoice.view

Retorna uma nota com itens, tributos e dados de autorização.

O status segue a máquina de estados do documento: draft → validating → queued → processing → authorized, com pending quando a conferência barra a nota por cadastro, rejected e error como desfechos da emissão e cancelling → cancelled no cancelamento (recusado, ele volta a authorized). Enquanto estiver em validating, queued ou processing, consulte — não reemita.

Na nota recusada, rejection traz a tradução do KontNotas (code, message e hint) e o que o provedor disse, cru: providerCode, providerMessage, field e details — todas as rejeições, cada uma com a correction que o município manda, quando manda. Oriente pelo code; mostre à pessoa o providerMessage. Os campos crus são null em nota rejeitada antes de 25/09/2026 e em falha técnica.

rejection
"rejection": {
  "code": "E0120",
  "message": "IM do prestador não deve ser informado, pois não existem…",
  "hint": "…",
  "providerCode": "E0120",
  "providerMessage": "(o texto do fisco, como veio)",
  "field": "IM",
  "details": [
    { "code": "E0120", "message": "(…)", "field": "IM", "correction": null }
  ]
}
GET/api/v1/invoices/{id}/file?tipo=pdf|xmlescopo invoice.view

Devolve o XML ou o PDF da nota no corpo da resposta.

Sem tipo, é o PDF. Nota inexistente, de outra organização ou arquivada responde 404 KONTNOTAS_INVOICE_NOT_FOUND; nota que ainda não tem o arquivo pedido responde 404 KONTNOTAS_NOT_FOUND — o XML fica disponível depois da autorização, e o PDF depende do provedor fiscal. Todo download entra na trilha de auditoria da organização.

POST/api/v1/invoices/{id}/cancelescopo invoice.cancel

Solicita o cancelamento, com motivo obrigatório.

O prazo e as regras de cancelamento são do município, não do KontNotas. Quando a nota está fora do prazo, a resposta explica isso em vez de tentar e falhar. A resposta é 202 com a nota em cancelling, até o provedor confirmar.

Clientes

POST/api/v1/customersescopo customer.manage

Cadastra um tomador. CPF e CNPJ são validados por dígito verificador.

Corpo
{
  "name": "Clínica Bella Estética Ltda.",
  "documentType": "CNPJ",
  "document": "11222333000181",
  "email": "financeiro@clinicabella.com.br",
  "address": {
    "street": "Avenida Paulista",
    "number": "1000",
    "district": "Bela Vista",
    "cityCode": "3550308",
    "state": "SP",
    "zipCode": "01310200"
  }
}
GET/api/v1/customersescopo customer.view

Lista e busca clientes por nome ou documento.

Serviços

GET/api/v1/servicesescopo service.view

Lista os serviços ativos — os que podem ir no item de uma nota.

Resposta
{
  "object": "list",
  "data": [
    {
      "object": "service",
      "id": "svc_01J...",
      "numero": 3,
      "name": "Toxina botulínica",
      "municipalServiceCode": "0602",
      "lc116Code": "06.02",
      "cnaeCode": "9602502",
      "issRateBps": 300,
      "fiscalConfigured": true
    }
  ],
  "total": 4,
  "hasMore": false
}

Serviços excluídos ficam de fora, como ficam da tela de emissão. fiscalConfigured só é verdadeiro com código de serviço e alíquota de ISS — sem os dois, a nota com esse serviço não passa pela conferência. Paginação por limit e offset.

numero é o número de cadastro — o “nº 3” que a tela mostra ao lado do nome. É por organização, estável e nunca reaproveitado, nem depois de o serviço ser excluído. O item da nota continua referenciando o serviço pelo id.

DELETE/api/v1/services/{id}escopo service.manage

Exclui o serviço do catálogo — arquivando, quando alguma nota já o usa.

Resposta
{ "object": "service", "id": "svc_01J...", "numero": 3, "deleted": true, "archived": true }

Serviço usado por alguma nota (rascunho incluído), recorrência ou automação é arquivado, com archived: true: sai do catálogo e da emissão, e as notas antigas continuam inteiras. Sem uso nenhum, é apagado de vez, com archived: false.

Id de outra organização, inexistente ou já excluído responde 404 KONTNOTAS_SERVICE_NOT_FOUND. Emitir com um serviço excluído responde 422 KONTNOTAS_SERVICE_ARCHIVED, sem criar a nota.

Organização e uso

GET/api/v1/organization

De quem é a chave, e se ela já emite. Responde a qualquer chave válida, sem escopo.

Resposta
{
  "object": "organization",
  "id": "org_01J...",
  "name": "Clínica Bella Estética Ltda.",
  "document": "11222333000181",
  "documentType": "CNPJ",
  "environment": "production",
  "certificate": { "status": "valid", "notAfter": "2027-03-12T00:00:00.000Z" },
  "fiscalReady": true,
  "pending": []
}

Do certificado saem só a situação e a validade — nada que o identifique. fiscalReady diz se uma NFS-e mandada com esta chave passaria pela configuração da organização, e pending lista, em texto, o que falta. O ambiente que vale é o da chave: uma chave kn_live_ numa organização ainda em testes aparece como pendência.

A conferência é a do pré-voo da emissão, na parte que não depende da nota — CNPJ e endereço da empresa, inscrição municipal, dados do Simples, certificado —, somada às credenciais do provedor e aos escopos da chave: sem invoice.create, invoice.issue e customer.manage, a emissão volta 403, e a chave aparece como pendência.

GET/api/v1/usageescopo invoice.view

A cota de notas do plano no período corrente.

Resposta
{
  "object": "usage",
  "period": "2026-09",
  "plan": { "key": "start", "name": "Start" },
  "invoices": { "used": 12, "limit": 30, "remaining": 18, "exceeded": false }
}

limit e remaining são null no plano ilimitado. exceeded é verdadeiro quando a próxima nota seria recusada pelo limite — com 30 de 30, já é. O período é o mês de apuração do contador, com a virada à meia-noite de Brasília.

Webhooks

Em vez de consultar em laço, receba os eventos. Configure os endpoints em Integrações → Webhooks, onde também ficam o histórico de entregas e o reenvio manual.

Cada entrega é assinada em X-KontNotas-Signature com HMAC-SHA256. O timestamp faz parte da mensagem assinada — o que impede replay de uma entrega capturada. Sempre valide a assinatura antes de agir sobre o payload, e compare em tempo constante.

Verificação (Node.js)
import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, header, secret) {
  const [tsPart, sigPart] = header.split(',');
  const timestamp = tsPart.replace('t=', '');
  const received = Buffer.from(sigPart.replace('v1=', ''), 'hex');

  // O timestamp entra na mensagem: sem isso, uma entrega antiga
  // continuaria com assinatura válida para sempre.
  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest();

  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  return fresh && received.length === expected.length && timingSafeEqual(received, expected);
}

O evento invoice.rejected traz, além de code, reason e fix, os mesmos campos crus da consulta — providerCode, providerMessage, field e details —, para ninguém precisar consultar a nota só para saber o que o fisco disse.

Responda 2xx rapidamente e processe depois: entregas sem resposta são reenviadas com backoff. Trate os eventos como at least once — use o id do evento para descartar repetições.