Erros
Todo erro tem o mesmo formato, um código estável e um request_id. Cite esse identificador no suporte: é com ele que achamos a chamada sem precisar do conteúdo da sua nota.
JSON
{
"api_version": "1",
"tables_version": "2026-10-11",
"code": "INVALID_INPUT",
"message": "Entrada inválida.",
"details": [{ "path": "competence_date", "expected": "data no formato AAAA-MM-DD" }],
"docs_url": "https://kalvi.com.br/docs/api/erros/INVALID_INPUT",
"request_id": "req_18c0fd46-0960-4d53-bc8f-1c0a6cde4b0a"
}Lista completa
| Código | HTTP | Quando acontece | O que fazer | Conta no plano |
|---|---|---|---|---|
INVALID_INPUT | 400 | Algum campo da entrada está ausente ou fora do formato. | Leia `details`: ele traz o caminho do campo e o que era esperado. Repetir sem corrigir dá o mesmo erro. | sim |
INVALID_JSON | 400 | O corpo não é um JSON válido. | Confira a serialização no seu lado. Costuma ser vírgula sobrando ou aspas não escapadas. | sim |
INVALID_BODY | 400 | O corpo chegou vazio, ou o JSON não tem `DPS` nem `notes`. | Mande o XML com `Content-Type: application/xml`, ou o objeto `{ "DPS": ... }`, ou o lote `{ "notes": [...] }`. | sim |
PARSE_ERROR | 400 | O XML da DPS não pôde ser lido. | `details` traz linha e coluna. Quase sempre é tag não fechada ou caractere inválido no meio do arquivo. | sim |
DTD_NOT_ALLOWED | 400 | O XML da DPS declara DTD (`<!DOCTYPE ...>`). | Remova a declaração. DTD é recusado de propósito: é por onde passa ataque de entidade externa. | sim |
ROOT_NOT_DPS | 400 | O documento até é XML, mas a raiz não é uma DPS. | Confira se você não mandou a NFS-e já autorizada, ou o envelope de transmissão, no lugar da DPS. | sim |
BATCH_TOO_LARGE | 400 | O lote passou de 50 notas. | Quebre em lotes de até 50. `details.max` traz o limite vigente. | sim |
KEY_LIMIT_REACHED | 400 | A conta já tem três chaves ativas. | Revogue uma chave antes de criar outra. | não |
UNAUTHORIZED | 401 | Chave ausente, inválida ou revogada; ou sessão do painel expirada. | Confira o cabeçalho `Authorization`. Não dizemos qual dos casos é, de propósito. | não |
INVALID_TOKEN | 401 | O link de entrada por e-mail é inválido, já foi usado ou expirou. | Peça outro link. Cada um vale 15 minutos e serve uma única vez. | não |
NOT_FOUND | 404 | A rota chamada não existe nesta versão da API. | Confira o caminho e a versão na URL (`/v1`). | sim |
TABLE_NOT_FOUND | 404 | A versão de tabela pedida não é servida. | `details.available` lista as versões existentes. Use `latest` se não quiser fixar uma. | sim |
PAYLOAD_TOO_LARGE | 413 | O corpo passou de 2 MB. | Quebre o lote. Uma DPS típica tem poucos kilobytes; 2 MB já são muitas notas. | sim |
RATE_LIMITED | 429 | Chamadas demais em pouco tempo (rajada), acima de 120 por minuto por chave. | Espere o que diz `Retry-After` e tente de novo. O SDK já faz isso sozinho. | não |
QUOTA_EXCEEDED | 429 | O limite mensal do plano acabou. | Repetir não adianta. `details` traz `upgrade_url` e `resets_at`, o dia em que o contador zera. | não |
EMAIL_NOT_CONFIGURED | 503 | Cadastro por e-mail pedido sem provedor de envio configurado. | É falha nossa de configuração, não sua. Avise o suporte com o `request_id`. | não |
DATABASE_NOT_CONFIGURED | 503 | Rota de conta chamada numa instância sem banco. | Falha nossa de configuração. Não conta no seu plano. | não |
INTERNAL_ERROR | 500 | Erro não previsto do nosso lado. | Tente de novo; o SDK repete sozinho. Se insistir, mande o `request_id` para o suporte. | não |
O que vale repetir
Vale repetir INTERNAL_ERROR, RATE_LIMITED e qualquer falha de rede: o SDK faz isso sozinho, com espera crescente e respeitando Retry-After. Não vale repetir erro de entrada (4xx) nem QUOTA_EXCEEDED — a resposta seria a mesma.
O que conta para o limite
Conta a chamada que devolveu resultado e a que foi recusada por entrada inválida. Falha nossa não conta e aparece na coluna “não cobradas” do extrato.
Apoio técnico, não consultoria tributária. A responsabilidade pela nota é do emissor.
