docs · api
EN
Fundamentos

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"
  }
}

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.codeStatusQuando 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. Quando true, aguarde retryDelay (ms) — ou o header Retry-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:

TypeScript
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() em try/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.