Tratamento de erros
Toda resposta de erro (4xx/5xx) segue um envelope
único:
success: false e um objeto error com mensagem, código
estável, indicação de retry e o identificador da
requisição.
Envelope de erro
{
"success": false,
"error": {
"message": "Post não encontrado",
"code": "NOT_FOUND",
"type": "not_found",
"statusCode": 404,
"timestamp": "2026-07-01T18:58:22.562Z",
"retryable": false,
"path": "/public/posts/abc123",
"requestId": "req_5c26469a-1234-4c8b-9e21-370450bbda3a",
"docs": "https://docs.draftin.io/errors/NOT_FOUND"
}
} {
"success": false,
"error": {
"message": "Erro de validação",
"code": "VALIDATION_ERROR",
"type": "field_validation",
"statusCode": 400,
"timestamp": "2026-07-01T18:58:22.562Z",
"retryable": false,
"details": {
"fields": [
{ "field": "q", "message": "Campo deve ter pelo menos 3 caracteres", "code": "too_small" }
],
"totalErrors": 1
},
"path": "/public/search",
"requestId": "req_5c26469a-1234-4c8b-9e21-370450bbda3a",
"docs": "https://docs.draftin.io/errors/VALIDATION_ERROR"
}
} {
"success": false,
"error": {
"message": "Você atingiu o limite de requisições.",
"code": "RATE_LIMIT_EXCEEDED",
"type": "rate_limit",
"statusCode": 429,
"timestamp": "2026-07-01T18:58:22.562Z",
"retryable": true,
"retryDelay": 60000,
"requestId": "req_5c26469a-1234-4c8b-9e21-370450bbda3a",
"docs": "https://docs.draftin.io/errors/RATE_LIMIT_EXCEEDED"
}
} Campos de error
messagestring
Mensagem legível para exibir ao usuário/desenvolvedor.
codestring
Código interno e estável do erro, seguro para tratamento programático (ex.:
NOT_FOUND, VALIDATION_ERROR).typestring
Categoria do erro (ex.:
not_found, field_validation, rate_limit), útil para decidir como reagir.statusCodeinteger
O status HTTP da resposta.
timestampstring · date-time
Momento do erro (ISO 8601, UTC).
retryableboolean
Indica se faz sentido repetir a requisição.
retryDelayintegernullable
Delay sugerido em milissegundos antes do retry (presente quando
retryable é true; espelhado no header Retry-After, em segundos).detailsobjectnullable
Detalhes estruturados. Em
VALIDATION_ERROR, contém fields: lista de { field, message, code }.pathstring
O path da requisição que falhou.
requestIdstring
Identificador único da requisição (também no header
X-Request-Id). Envie ao suporte para correlacionar logs.docsstring
Link para a documentação do código de erro específico.
Códigos de erro comuns
| error.code | Status | Quando ocorre |
|---|---|---|
| VALIDATION_ERROR | 400 | Parâmetro ou corpo inválido: q com menos de 3 caracteres,
format desconhecido, slots vazio/acima de 20,
clickToken ausente ou expirado. details.fields lista os campos problemáticos. |
| UNAUTHORIZED | 401 | Chave de API ausente ou inválida no header X-API-Key. |
| FORBIDDEN | 403 | Chave válida, mas sem permissão para a ação — por exemplo, origem (Origin/Referer) não autorizada no clique de anúncio. |
| NOT_FOUND | 404 | Recurso inexistente ou não publicado para o ID/slug informado. |
| METHOD_NOT_ALLOWED | 405 | Verbo HTTP não suportado no path. O header Allow lista os
métodos aceitos. |
| CONFLICT | 409 | clickToken já utilizado (uso único) no tracking de clique. |
| UNSUPPORTED_MEDIA_TYPE | 415 | Requisição com corpo sem Content-Type: application/json. |
| RATE_LIMIT_EXCEEDED | 429 | Limite de requisições excedido. retryable: true;
espere o valor do header Retry-After. |
| INTERNAL_ERROR | 500 | Falha inesperada no servidor. Transitório: repita com backoff. |
Retry: quando e como repetir
- Confie em
error.retryable. Quandotrue, aguarderetryDelay(ms) — ou o headerRetry-After(segundos) — antes de tentar de novo. - Não repita 4xx (exceto
429): erros de cliente não se resolvem com retentativa; corrija a entrada. - Repita 5xx com backoff exponencial e um teto de tentativas. Há um wrapper pronto em Rate limits.
requestId e suporte
Toda resposta — sucesso ou erro — carrega o header X-Request-Id, espelhado em
error.requestId nos erros. Registre esse valor nos seus logs; ao
abrir um chamado de suporte, envie-o para que a equipe do DraftIn localize a requisição
exata.
Um tratador reutilizável
Centralize a decisão por status/código e a leitura tolerante do corpo em uma função:
interface ApiErrorBody {
success: false;
error: {
message: string;
code: string;
statusCode: number;
retryable: boolean;
retryDelay?: number;
requestId: string;
};
}
async function draftin<T>(path: string): Promise<T> {
const res = await fetch("https://api.draftin.io" + path, {
headers: { "X-API-Key": process.env.DRAFTIN_API_KEY! },
});
if (res.ok) {
const { data } = (await res.json()) as { data: T };
return data;
}
// Leitura tolerante: nem todo erro traz o envelope completo.
let code = String(res.status);
let detalhe = res.statusText;
try {
const body = await res.json();
code = body?.error?.code ?? code;
detalhe = body?.error?.message ?? body?.error ?? detalhe;
} catch { /* corpo vazio ou não-JSON */ }
switch (code) {
case "VALIDATION_ERROR": throw new Error(`Requisição inválida: ${detalhe}`);
case "UNAUTHORIZED": throw new Error("Chave de API inválida ou ausente.");
case "FORBIDDEN": throw new Error("Sem permissão para este recurso.");
case "NOT_FOUND": throw new Error("Recurso não encontrado.");
case "RATE_LIMIT_EXCEEDED": {
const after = res.headers.get("Retry-After");
throw new Error(`Limite de requisições; tente em ${after ?? "?"}s.`);
}
default: throw new Error(`Erro ${res.status} (${code}): ${detalhe}`);
}
} Princípios
- Status primeiro, corpo depois. Ramifique pelo código HTTP;
use o corpo para o tratamento fino via
error.code. - Leitura tolerante. Envolva
res.json()emtry/catch— o corpo pode estar vazio ou fora do envelope. - Não repita 4xx (exceto
429); corrija a entrada. - Repita 429/5xx com backoff, respeitando
Retry-After.