Erros e Rejeições
Referência completa de erros HTTP e rejeições SEFAZ. Causas, soluções e estratégia de retry.
Erros e Rejeições
Toda resposta de erro da engineAPI segue o padrão RFC 7807 (Problem Details),
aplicado globalmente pelo HttpExceptionFilter. Não existe o formato antigo
{statusCode, error, message, sefazCode, sefazMessage}: esses campos não existem
no contrato real.
Formato de Erro (RFC 7807)
{
"error": {
"type": "https://engineapi.com.br/errors/BAD_REQUEST",
"title": "Requisição Inválida",
"status": 400,
"detail": "SEFAZ rejeitou (CStat=539): Rejeicao: Duplicidade de NF-e",
"erros": [
{ "codigo": "539", "descricao": "Rejeicao: Duplicidade de NF-e" }
],
"instance": "/v1/nfe",
"requestId": "req_uofirusmuamw",
"timestamp": "2026-07-05T11:06:14.453Z"
}
}
| Campo | Sempre presente? | Descrição |
|---|---|---|
error.type | Sim | URI do tipo de erro (https://engineapi.com.br/errors/<CODIGO>) |
error.title | Sim | Título legível do status HTTP |
error.status | Sim | Código HTTP (espelha o status da resposta) |
error.detail | Sim | Mensagem legível |
error.errors[] | Só em erro de validação (400 de payload malformado) | { field, message } por campo inválido; message sempre em português (ex.: "Tipo inválido: esperado texto, recebido nulo", "Campo obrigatório"), inclusive nos defaults de tipo/obrigatoriedade |
error.erros[] | Só em rejeição fiscal (SEFAZ/SEFIN) | { codigo, descricao }, passthrough verbatim, sem tradução |
error.details.camposDesconhecidos[] | Só quando o payload traz campo que o contrato não conhece | { campo, caminho, objeto, motivo? } por campo recusado. Ver Campo desconhecido no payload |
error.instance | Sim | Path da requisição (já com /v1) |
error.requestId | Sim | Mesmo valor do header X-Request-Id |
error.timestamp | Sim | ISO 8601 |
Não existem sefazCode/sefazMessage no envelope. O código e a mensagem da SEFAZ vêm
em error.erros[].codigo e error.erros[].descricao.
Como o slug de error.type nasce
O último segmento de error.type (o <SLUG> em .../errors/<SLUG>) é resolvido nesta
ordem, pelo filtro global de exceções:
- Se a exceção carrega um
code(ouerror) explícito, entre eles os códigos de negócio deste catálogo (CERTIFICADO_AUSENTE,CNPJ_CONFLICTetc.), o slug é esse valor, normalizado (maiúsculas, espaços viram_). - Sem
code/errorexplícito, o slug cai num mapa fixo por status HTTP (400viraBAD_REQUEST,422viraUNPROCESSABLE_ENTITY,429viraRATE_LIMIT_EXCEEDEDetc.). - Payload de validação em formato de array (rejeição da validação de schema, global
a toda a API) sempre vira
VALIDATION_ERROR, independente do status.
errors[] (validação) vs erros[] (fiscal): nunca os dois juntos
Os dois arrays de erro do envelope têm formato e origem diferentes e uma resposta carrega no máximo um dos dois:
error.errors[] | error.erros[] | |
|---|---|---|
| Quando aparece | Erro de validação do payload (campo ausente/tipo errado) | Rejeição fiscal (SEFAZ/SEFIN) |
| Shape | { field, message } | { codigo, descricao } |
| Idioma | message sempre em português, traduzido pela engineAPI | descricao verbatim da SEFAZ/SEFIN, sem tradução |
Slug de error.type | Sempre VALIDATION_ERROR | Varia (BAD_REQUEST na rejeição de emissão; outro slug em endpoints específicos) |
null, "" e campo ausente no PATCH
O GET devolve null em todo campo que ainda não foi preenchido. Como a integração
costuma ler o recurso, mudar um campo e devolver o objeto inteiro, o PATCH de emissor
(PATCH /v1/companies/{id}) trata os três casos assim:
| No corpo | Efeito |
|---|---|
| Chave ausente | Mantém o valor atual |
null | Mantém o valor atual (o roundtrip do GET não apaga nada) |
"" (string vazia) | Limpa o campo |
// roundtrip do GET: os campos não preenchidos voltam null e são ignorados
const { data } = await api.get(`/v1/companies/${id}`);
await api.patch(`/v1/companies/${id}`, { ...data, ie: "123456789" }); // 200
// equivalente e mais enxuto: mande só o que mudou
await api.patch(`/v1/companies/${id}`, { ie: "123456789" });
null só desliga a escrita daquele campo. Não afrouxa validação: valor não-nulo
continua passando por DV do CNPJ, formato e limite de tamanho, e erro de conteúdo segue
400 com error.errors[].
Campo desconhecido no payload (400)
O contrato de emissão é estrito: campo que a engineAPI não conhece devolve 400 e
nada é emitido (nenhum número fiscal é consumido, nenhuma fatura é criada). Vale para
POST /v1/nfe, POST /v1/nfe/batch, POST /v1/nfce e POST /v1/nfse.
A recusa acontece antes de qualquer processamento, inclusive quando você usa
resolverTributacao: true.
{
"error": {
"type": "https://engineapi.com.br/errors/VALIDATION_ERROR",
"title": "Requisição Inválida",
"status": 400,
"detail": "Os dados enviados são inválidos: há campo não reconhecido no corpo da requisição",
"errors": [
{
"field": "items.0",
"message": "items[0]: campo não reconhecido: \"NFref\". Sobre \"NFref\": A NF-e referenciada (devolução, complementar e ajuste) ainda não é suportada pela engineAPI. Enviar o campo não geraria o grupo no documento e a SEFAZ rejeitaria a nota com cStat 321 depois de consumir o número fiscal. Campos aceitos em items[0]: codigo, ean, descricao, ncm, cest, cfop, unidade, quantidade, valorUnitario, valorTotal, desconto, icms, pis, cofins, ipi, ibsCbs. A engineAPI recusa campo desconhecido em vez de descartar em silêncio: campo fiscal ignorado sem aviso vira documento errado."
}
],
"details": {
"camposDesconhecidos": [
{
"campo": "NFref",
"caminho": "items[0].NFref",
"objeto": "items[0]",
"motivo": "A NF-e referenciada (devolução, complementar e ajuste) ainda não é suportada pela engineAPI. Enviar o campo não geraria o grupo no documento e a SEFAZ rejeitaria a nota com cStat 321 depois de consumir o número fiscal."
}
]
},
"instance": "/v1/nfe",
"requestId": "req_uofirusmuamw",
"timestamp": "2026-08-04T12:00:00.000Z"
}
}
Campo de camposDesconhecidos[] | Descrição |
|---|---|
campo | Nome da chave recusada, exatamente como você enviou |
caminho | Caminho completo até a chave (ex.: items[0].icms.pRedBC) |
objeto | Objeto que recusou (ex.: items[0].icms). Vazio quando a chave está na raiz do corpo |
motivo | Presente quando o campo existe no leiaute fiscal: ou o grupo ainda não é suportado, ou ele existe no contrato com outro nome |
Use caminho para localizar o campo no seu payload e motivo para decidir o que fazer:
sem motivo, é nome errado ou campo que não existe (a mensagem sugere o campo aceito mais
parecido, quando há um, e avisa quando a diferença é só a caixa das letras). Com motivo,
leia a frase: ela diz se o grupo não existe ainda ou para onde ir.
Campos do leiaute que existem no contrato com outro nome
O corpo da requisição não é o XML. Alguns grupos existem, com nome de campo próprio:
| Você mandou (nome do XML) | Use no corpo |
|---|---|
comb, cProdANP, descANP, pGLP, pGNn, pGNi, vPart, UFCons | items[].combustivel, com os mesmos campos dentro dele. Ver o guia de Combustíveis e GLP e a referência de campos |
Campos do leiaute que a engineAPI ainda não suporta
Estes são campos reais da NF-e. Enviar qualquer um deles devolve 400 com o motivo
específico, em vez de a nota sair sem o grupo:
| Grupo | Campos que disparam a recusa | Situação |
|---|---|---|
| Documento referenciado | NFref, refNFe, refNF, refNFP, refCTe, refECF | Devolução, complementar e ajuste ainda não são suportados |
| DIFAL | ICMSUFDest, vBCUFDest, pICMSUFDest, vICMSUFDest, vICMSUFRemet, pFCPUFDest | Ainda não suportado. A operação que exige o grupo é recusada com 422 |
| Transporte detalhado | veiculo, veicTransp, placa, rntc, reboque, lacres, retTransp, balsa, vagao | Em transporte, hoje o contrato aceita modFrete, transportadora e volumes |
| Rastreabilidade | rastro, nLote, qLote, dFab, dVal | Ainda não suportado |
| Medicamentos | med, cProdANVISA, vPMC | Ainda não suportado |
| Informação por item | infAdProd, gta | Ainda não suportado. É onde entraria a Guia de Trânsito Animal |
| Benefícios de ICMS | pRedBC, vICMSDeson, motDesICMS, vICMSDif, pDif | CST 20, 40, 41 e 51 ainda não são suportados |
| Importação e exportação | DI, adi, detExport, exporta | Ainda não suportado |
A lista acima muda quando um grupo passa a ser suportado. O campo sai da recusa e entra no contrato, e a referência de campos passa a listá-lo.
O que muda para quem já integra
Se o seu payload usa apenas campos documentados, nada muda: mesma requisição, mesma resposta.
Se você envia algum campo a mais, o que antes era ignorado em silêncio agora é 400. Foi
uma decisão deliberada, e o motivo é o desfecho que o silêncio produzia:
| Você enviava | Antes | Agora |
|---|---|---|
NFref numa devolução | Campo descartado, nota transmitida, SEFAZ rejeitava com cStat 321 depois de consumir o número | 400 antes de consumir número |
veiculo no transporte | Campo descartado, SEFAZ rejeitava com cStat 544 | 400 com a lista do que o transporte aceita |
Grupo comb num GLP | Campo descartado e a nota saía autorizada sem o grupo obrigatório, fiscalmente irregular | 400 apontando o campo certo: items[].combustivel |
| Campos de DIFAL | Campos descartados, seguido de 422 genérico | 400 nomeando cada campo |
No lote (POST /v1/nfe/batch) a recusa é tudo ou nada: uma chave desconhecida em uma
única nota recusa o envio inteiro com 400, e nenhuma nota é enfileirada. Antes o lote
respondia 201 e descartava a chave. O caminho é o mesmo do singular: caminho em
camposDesconhecidos diz a nota pelo índice (ex.: notas[2].items[0].comb).
Checklist de migração:
- Rode seus payloads de homologação uma vez. Campo a mais aparece em
details.camposDesconhecidoscom o caminho exato. - Remova os campos sem
motivo(nome errado ou campo inexistente). - Para os campos com
motivo, o grupo ainda não é suportado: retire do payload e trate o cenário fora da API até o suporte existir.
Códigos HTTP
| Código | Significado | Quando ocorre |
|---|---|---|
200/201 | Sucesso | Requisição processada com sucesso |
400 | Bad Request | JSON inválido, campo obrigatório ausente/nome errado, ou rejeição da SEFAZ/SEFIN (error.erros[]) |
401 | Unauthorized | Token ausente, inválido ou expirado |
402 | Payment Required | Assinatura com pagamento pendente (PAYMENT_REQUIRED). Só bloqueia métodos que não são GET |
403 | Forbidden | Sem permissão, assinatura cancelada/inexistente, ou ek_test_ usada contra emissor de produção |
404 | Not Found | Recurso (nota, empresa, webhook, CNPJ/CPF consultado) não encontrado. Inclusive para não revelar recursos de outro partner |
409 | Conflict | CNPJ já cadastrado como emissor: CNPJ_CONFLICT, mesmo code/mensagem seja o CNPJ seu ou de outro parceiro (anti-enumeração, #383) |
413 | Payload Too Large | Upload de certificado A1 acima de 5MB (PAYLOAD_TOO_LARGE) |
422 | Unprocessable Entity | Sempre ANTES de qualquer chamada à SEFAZ/SEFIN/worker/provedor externo. Ver Catálogo de códigos de negócio para a lista completa de origens |
429 | Too Many Requests | Rate limit do plano excedido (RATE_LIMIT_EXCEEDED), ou rate limit do provedor de consulta externa de CNPJ/CPF (TOO_MANY_REQUESTS) |
500 | Internal Server Error | Erro interno. Contate o suporte |
502 | Bad Gateway | Falha ao consultar provedor externo (CNPJ/CPF) sem categoria mais específica |
503 | Service Unavailable | SEFAZ/SEFIN, ou o provedor externo de consulta de CPF, temporariamente indisponível |
Rejeição da SEFAZ/SEFIN é sempre 400, nunca 422. Todo 422 desta API acontece
ANTES de qualquer chamada à SEFAZ/SEFIN/worker ACBr/provedor externo: a validação é
sempre local. Não trate rejeição fiscal e 422 como sinônimos.
Catálogo de códigos de negócio
Todo código abaixo é o code (ou error) que vira o slug de error.type (ver
Como o slug nasce). É o catálogo completo dos
codes de negócio hoje na API, fora dos slugs genéricos por status
(BAD_REQUEST, UNAUTHORIZED, VALIDATION_ERROR etc., já cobertos acima).
Código (error.type slug) | HTTP | Quando ocorre |
|---|---|---|
CERTIFICADO_AUSENTE | 422 | Certificado A1 não instalado para o emissor. Ver Pré-voo do emissor abaixo |
CADASTRO_EMISSOR_INCOMPLETO | 422 | IE ou endereço do emissor faltando. Ver Pré-voo do emissor abaixo |
CSC_AUSENTE | 422 | Só na NFCe. csc/cscId não configurados no emissor. Ver Pré-voo do emissor abaixo |
TRIBUTACAO_NAO_RESOLVIDA | 422 | Emissão assistida (resolverTributacao: true) sem fonte para resolver um campo fiscal. Ver Emissão assistida acima |
RETENCOES_NAO_SUPORTADAS | 422 | Só na NFSe. POST /v1/nfse com retencoes.{irrf,csll,cofins,pis,inss,outrasRetencoes} informado com valor ≠ 0. Interino: o motor ainda não escreve grupos de retenção na DPS do Padrão Nacional; aceitar e descartar em silêncio seria pior que recusar. Recusa ANTES de alocar nDPS/transmitir/persistir; omita o bloco (ou mande tudo 0/ausente) |
IBSCBS_DPS_DESTINATARIO_NAO_SUPORTADO | 422 | Só na NFSe. POST /v1/nfse com ibsCbs.indDest: "1" (o destinatário do serviço não é o tomador). O leiaute da DPS exige, junto, o grupo dest (CNPJ/CPF/NIF, nome e endereço do destinatário), que este motor ainda não escreve; preenchê-lo com os dados do tomador seria declarar um fato fiscal que você não informou. Use "0" quando o destinatário for o próprio tomador. Recusa ANTES de alocar nDPS/transmitir/persistir. Ver Reforma Tributária: datas que importam |
CNAE_NAO_CADASTRADO | 422 | POST /v1/fiscal/resolve-servico-padrao com issuerId de um emissor seu sem CNAE cadastrado. Ver Serviço Padrão por CNAE |
LGPD_CONSENT_REQUIRED | 422 | GET /v1/queries/cpf/:cpf sem o header X-LGPD-Consent: true. Ver Outros códigos de negócio abaixo |
LGPD_PURPOSE_REQUIRED | 422 | GET /v1/queries/cpf/:cpf sem o header X-LGPD-Purpose (ou com valor fora da lista: emissao_nfe, cadastro, cobranca, obrigacao_legal, contrato, credito). Ver Outros códigos de negócio abaixo |
CNPJ_CONFLICT | 409 | POST /v1/companies (ou PATCH) com um CNPJ já cadastrado como Issuer: mesmo code e mensagem seja o CNPJ seu (recuperável, veja GET /v1/companies) ou de outro parceiro (Issuer.cnpj é único globalmente na base, não por partner; use POST /v1/companies/transfer-request). Anti-enumeração (#383): antes havia dois codes distintos, colapsados neste único valor. Ver Outros códigos de negócio abaixo |
NUMERO_JA_UTILIZADO | 409 | POST /v1/nfe ou POST /v1/nfce com um numero explícito que já foi usado por outro documento do mesmo emissor, série e modelo. Nada é emitido e nenhum número novo é consumido: use outro número ou omita o campo numero para a numeração automática. Ver Outros códigos de negócio abaixo |
DANFE_INDISPONIVEL | 409 | GET /v1/nfe/pdf/{accessKey} ou GET /v1/nfce/pdf/{accessKey} de um documento cujo XML autorizado não está armazenado neste ambiente (ex.: nota emitida em sandbox). O DANFE é gerado a partir do XML autorizado, sem ele não há documento |
PAYMENT_REQUIRED | 402 | Assinatura com pagamento pendente. Só métodos diferentes de GET são bloqueados. Ver Outros códigos de negócio abaixo |
TOO_MANY_REQUESTS | 429 | Rate limit do provedor externo de consulta de CNPJ (GET /v1/queries/cnpj/:cnpj). Distinto do RATE_LIMIT_EXCEEDED do seu próprio plano. Ver Outros códigos de negócio abaixo |
COMBUSTIVEL_GRUPO_OBRIGATORIO | 422 | POST /v1/nfe com um item cujo CFOP é de operação com combustível (um dos 63 códigos com indComb = 1 ou 2 na Tabela CFOP) e que não traz o bloco combustivel. A SEFAZ rejeitaria com 660; recusamos antes, sem consumir número fiscal. Ver Combustíveis |
COMBUSTIVEL_INVALIDO | 422 | POST /v1/nfe com o bloco combustivel que o documento não consegue representar: percentuais de GLP em produto que não é GLP (461), soma pGLP + pGNn + pGNi diferente de 100 nas 4 casas gravadas no documento (855), GLP sem vPart ou com valor que arredonda para 0,00 (856), GLP com unidade diferente de kg (854), cProdANP com 9 dígitos mas todos zeros (o grupo sumiria do documento, 660) ou ufConsumo inválida. Ver Combustíveis |
LOTE_ACIMA_DO_LIMITE | 422 | POST /v1/nfe/batch com mais notas do que o máximo por chamada. details.maximoPorLote e details.enviadas dizem o limite e o que você mandou. Nada do envio é enfileirado e nenhum número fiscal é consumido. Ver Emissão em lote |
ISSUER_DIVERGENTE_NO_LOTE | 422 | POST /v1/nfe/batch em que uma das notas traz issuerId diferente do emissor do lote. Um lote emite por um emissor: use o issuerId na raiz do corpo e mande um lote por emissor. Nada é enfileirado |
EMISSOR_INEXISTENTE | 404 | Item da fila (GET /v1/nfe/queue) cujo emissor foi removido depois do enfileiramento. Desfecho FAILED permanente, sem retry, e nenhum número fiscal é consumido. Aparece em resultData.error, não como resposta HTTP |
PAYLOAD_TOO_LARGE | 413 | Corpo da requisição acima do limite. Dois produtores: upload de certificado A1 (POST /v1/companies/{id}/certificate) acima de 5MB, e corpo JSON acima de 100 kB em qualquer endpoint, típico de POST /v1/nfe/batch com muitas notas de muitos itens. details.limiteBytes/details.tamanhoBytes dizem o limite e o enviado; divida o envio |
VALIDATION_ERROR | 400 | Payload rejeitado pela validação de schema. Ver errors[] vs erros[] acima |
CNPJ_CONFLICT sai com code e message idênticos ("CNPJ já cadastrado para
este parceiro") seja o CNPJ do próprio parceiro ou de outro. É anti-oráculo: a API
nunca deixa um CNPJ de terceiro virar uma forma de descobrir se ele já tem emissor
cadastrado em outro parceiro da plataforma, nem pela mensagem, nem pelo code.
Os erros de consulta externa (GET /v1/queries/cnpj/:cnpj e GET /v1/queries/cpf/:cpf,
Receita Federal/SerPro) que não batem com nenhum code de negócio caem nos slugs
genéricos por status: NOT_FOUND (CNPJ/CPF não encontrado), BAD_GATEWAY (falha sem
categoria mais específica) ou SERVICE_UNAVAILABLE (provedor fora do ar).
Rejeição SEFAZ/SEFIN (400): exemplos
Independente do módulo (NFe, NFCe, NFSe), a rejeição fiscal chega como 400 com
error.erros[]. Alguns códigos comuns:
Emissão assistida (422): quando aparece
Só ocorre com resolverTributacao: true no payload de NFe/NFCe/NFSe. Se um campo fiscal
obrigatório (ex.: CSOSN, grupo IBS/CBS, cTribNac) não tiver fonte para ser resolvido
pelo Cérebro Fiscal, a API responde 422 e nada é emitido/persistido:
{
"error": {
"type": "https://engineapi.com.br/errors/TRIBUTACAO_NAO_RESOLVIDA",
"title": "Entidade Não Processável",
"status": 422,
"detail": "Tributação assistida: 2 item(ns) não resolvidos. Nada foi emitido. Corrija os itens ou informe a tributação (csosn/ibsCbs) manualmente.",
"instance": "/v1/nfe",
"requestId": "req_abc123",
"timestamp": "2026-07-06T12:00:00.000Z"
}
}
Na NFe/NFCe, o corpo real inclui {index, motivo} por item não-resolvido; na NFSe,
camposNaoResolvidos com {campo, motivo}. Corrija o cadastro do emissor
(servicoPadraoLc116/cTribNacPadrao) ou informe o campo manualmente, e reenvie.
Espelho fiscal divergente
Um dos motivos possíveis dentro de details.itensNaoResolvidos: a redução de IBS/CBS
que temos espelhada para o NCM não bate com a tabela oficial de classificação
tributária para a classe (cClassTrib) daquele mesmo NCM. Recusamos com este 422
antes de transmitir: a SEFAZ rejeitaria a mesma incoerência com cStat
1034/1046/1063 (IBS-UF/IBS-Municipal/CBS), só que sem contexto nenhum.
Como seguir agora: informe o grupo ibsCbs do item explicitamente (override do
parceiro, o motor não recalcula nem consulta o espelho para ele), ou emita esse item
sem resolverTributacao, com a tributação que o seu ERP já tem.
Exemplo de payload, o motivo completo de recusar antes de transmitir, e como um mesmo NCM pode ter mais de um anexo de redução: Reforma Tributária: erros de IBS/CBS.
NCM multiclasse
Outro motivo dentro de details.itensNaoResolvidos, e o mais comum em alimento: o NCM
aparece em mais de um anexo da LC 214/2025, com percentuais diferentes. Quem decide
qual vale é o produto real, não o código: a resposta traz candidatas[] com todas as
classes vigentes; informe ibsCbs.cClassTrib para desempatar.
Exemplo completo de payload/resposta, a tabela dos 4 codes irmãos
(NCM_MULTICLASSE, NCM_SEM_CLASSE_AUTOMATICA, CCLASSTRIB_INVALIDO_PARA_NCM,
CCLASSTRIB_INADMISSIVEL_NO_MODELO) e as três regras de informar só a classe:
Reforma Tributária: erros de IBS/CBS.
Pré-voo do emissor (422): quando aparece
Antes de acionar o worker ACBr, a engineAPI valida se o cadastro do emissor (NFe e NFCe, o mesmo cadastro) tem o que o documento fiscal exige. Duas checagens, mesmo contrato de resposta:
Outros códigos de negócio
Estratégia de Retry
| Tipo de erro | Retry? | Quando |
|---|---|---|
400 (validação ou rejeição SEFAZ) | Não | Corrija os dados primeiro. Reenvio com a mesma Idempotency-Key devolve a mesma rejeição, sem retransmitir à SEFAZ |
401 Unauthorized | Não | Faça login e obtenha novo token |
403 Forbidden | Não | Verifique permissões do plano ou o ambiente da API Key |
404 Not Found | Não | Recurso não existe |
422 Pré-voo (assistida/certificado/cadastro) | Não | Corrija o cadastro do emissor (IE, endereço, certificado) ou complete o campo manualmente |
429 Rate Limit | Sim | Aguarde o tempo do header Retry-After |
500 Server Error | Sim | Retry com backoff exponencial |
503 SEFAZ/SEFIN indisponível | Sim | Retry após alguns minutos |
Implementando Retry com Backoff Exponencial
async function emitirComRetry(data: any, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const resp = await fetch('https://api.engineapi.com.br/v1/nfe', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Idempotency-Key': `pedido-${data.pedidoId}-nfe`,
},
body: JSON.stringify(data),
});
if (resp.ok) return await resp.json();
const body = await resp.json();
// Não fazer retry em erros de dados/rejeição/permissão
if ([400, 401, 403, 404, 422].includes(resp.status)) {
throw new Error(`Erro não recuperável: ${body.error?.detail}`);
}
// Retry em 429/500/503
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
} catch (err) {
if (attempt === maxRetries - 1) throw err;
}
}
}
Rate Limits
| Plano | Requests/segundo |
|---|---|
| Dev | 5 |
| Starter | 20 |
| Growth | 60 |
| Scale | 200 |
| Enterprise | Dedicado |
Não existe limite por minuto, hora ou dia: a janela é sempre de 1 segundo. Quando o limite
é excedido, o erro também segue o envelope RFC 7807 (status: 429), com o header
Retry-After (sempre 1, em segundos) indicando quando tentar novamente.