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.
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.
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.
{
"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.
{
"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
/api/v1/invoicesescopo invoice.createCria 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.
{
"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": { "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" } }/api/v1/invoicesescopo invoice.viewLista 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.
{
"object": "list",
"data": [ { "object": "invoice", "id": "inv_01J...", "status": "authorized", … } ],
"total": 128,
"hasMore": true
}/api/v1/invoices/{id}escopo invoice.viewRetorna 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": {
"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 }
]
}/api/v1/invoices/{id}/file?tipo=pdf|xmlescopo invoice.viewDevolve 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.
/api/v1/invoices/{id}/cancelescopo invoice.cancelSolicita 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
/api/v1/customersescopo customer.manageCadastra um tomador. CPF e CNPJ são validados por dígito verificador.
{
"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"
}
}/api/v1/customersescopo customer.viewLista e busca clientes por nome ou documento.
Serviços
/api/v1/servicesescopo service.viewLista os serviços ativos — os que podem ir no item de uma nota.
{
"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.
/api/v1/services/{id}escopo service.manageExclui o serviço do catálogo — arquivando, quando alguma nota já o usa.
{ "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
/api/v1/organizationDe quem é a chave, e se ela já emite. Responde a qualquer chave válida, sem escopo.
{
"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.
/api/v1/usageescopo invoice.viewA cota de notas do plano no período corrente.
{
"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.
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.