Códigos de erro

Erros gerados pelo gateway usam o mesmo envelope:

{
  "error": {
    "code": "document_required",
    "message": "A J&T Express exige o documento do destinatário no header X-Tracking-Document.",
    "param": "X-Tracking-Document",
    "courier": "jtexpress",
    "doc_url": "{PUBLIC_APP_URL}/?docs=errors#document_required",
    "request_id": "0ebcea5b-1727-4bf9-af88-4deb11b438f2"
  }
}
Campo Sempre presente Descrição
code Sim Identificador estável. É nele que sua aplicação deve decidir.
message Sim Texto legível em português. Pode ser reescrito a qualquer momento — não compare esta string.
request_id Sim Igual ao header X-Request-Id. Cite ao abrir um chamado.
param Não Parâmetro, segmento de rota ou header que causou o erro
courier Não Transportadora envolvida, quando houver
doc_url Não Link direto para a seção correspondente no portal público

Nas rotas normalizadas, erros não chegam com status 2xx. Nas rotas /raw, o payload original pode conter um erro da transportadora mesmo com HTTP 200; nesse caso ele não usa este envelope. Verifique o formato da transportadora antes de tratar res.ok como rastreamento encontrado. Veja Resposta crua.

Lista completa

Status code Quando acontece Consome cota
400 invalid_request Requisição malformada que nenhum outro código descreve, incluindo caminho acima de 1.024 caracteres Não
400 invalid_tracking_code Código informado acima de 40 caracteres, com caracteres inválidos (inclusive espaços) ou fora do formato aceito pela transportadora Não
400 invalid_retry_level retry_level fora de low, medium, high Não
400 courier_not_supported Slug de transportadora fora do catálogo Não
401 missing_api_key Header X-API-Key ausente Não
401 invalid_api_key Chave desconhecida ou revogada Não
403 subscription_inactive Sem assinatura ativa Não
403 account_suspended A conta foi suspensa. Entre em contato com o suporte Não
403 phone_verification_required Plano Básico sem telefone verificado Não
404 not_found O caminho não é um endpoint da API, inclusive /v1/track/ sem o segmento do código Não
404 tracking_not_found A transportadora foi consultada e não tem registro do código Sim
405 method_not_allowed O endpoint existe, mas aceita apenas GET Não
413 payload_too_large Corpo acima de 1 MiB (1.048.576 bytes) ou sem tamanho confiável Não
422 courier_not_detected Código bem formado que não corresponde a nenhuma transportadora suportada Não
422 document_required Requisição J&T Express sem X-Tracking-Document Não
422 invalid_document Reservado para quando a transportadora identificar que recusou o documento Sim
429 rate_limit_exceeded Limite por minuto atingido. Traz Retry-After Não
429 quota_exceeded Cota do período esgotada Não
500 internal_error Falha nossa Não
502 courier_unavailable Transportadora indisponível ou com resposta ininterpretável Não
503 service_unavailable Nosso serviço degradado. Traz Retry-After Não
504 courier_timeout O orçamento de tempo do retry_level se esgotou Não

Detalhes de cobrança em Cobrança de requisições.

Como interpretar cada família

  • 4xx — algo na requisição precisa mudar. Repetir sem alteração produz o mesmo erro. As exceções são 429 (espere) e 404 tracking_not_found (o código pode passar a existir mais tarde).
  • 5xx — o problema é nosso ou da transportadora, e nenhuma delas é cobrada. Repetir mais tarde é razoável.

Tratamento recomendado para rotas normalizadas

const res = await fetch(url, { headers: { "X-API-Key": key } });

if (res.ok) {
  return res.json();
}

const { error } = await res.json();

switch (error.code) {
  case "tracking_not_found":
    return null;                                   // sem informação para este código

  case "courier_not_detected":
    throw new Error("Informe a transportadora na rota para este código.");

  case "document_required":
    throw new Error("Reenvie com o header X-Tracking-Document.");

  case "rate_limit_exceeded": {
    const wait = Number(res.headers.get("Retry-After") ?? 1);
    await sleep(wait * 1000);
    return retry();
  }

  case "quota_exceeded":
    throw new Error("Cota do período esgotada — faça upgrade do plano.");

  case "account_suspended":
    // Repetir não resolve: só a remoção da suspensão restabelece o acesso.
    throw new Error("Conta suspensa — entre em contato com o suporte.");

  case "courier_unavailable":
  case "courier_timeout":
  case "service_unavailable":
    return retryLater();                           // falha temporária, não é cobrada

  default:
    throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}

Compatibilidade

Novos code podem ser adicionados sem aviso. Sempre tenha um caminho default que trate pelo status HTTP — um code desconhecido nunca deve derrubar sua integração.