Erros
Formato real das respostas de erro da API da Axuz, mapa de status HTTP, o 404 de recurso inexistente versus rota inexistente, rastreio por X-Trace-Id e quando repetir uma chamada.
Toda resposta de erro da API vem com um status HTTP e, quase sempre, um corpo JSON pequeno. Decida o que fazer pelo status; use o texto do corpo para mostrar ao usuário ou para o log, não como contrato.
O formato
O formato canônico é um objeto com a chave error:
{ "error": "task not found" }
Isso vale para o serviço principal, para o de workspace, calendário, formulários, arquivos e cobrança. Alguns erros acrescentam campos próprios ao lado de error:
| Situação | Corpo |
|---|---|
422 de validação (helper UnprocessableEntity) | {"error": "name_required"}, e opcionalmente "message" com o detalhe |
429 do token pessoal | {"error": "rate_limited", "limit": 60, "count": 61, "retry_after": 12} |
429 de mutações em rotas de equipe | {"error": "rate_limited", "retry_after": 12} |
429 de bloqueio por IP no serviço de autenticação | {"error": "ip_blocked", "retry_after_seconds": 300} |
503 do limitador indisponível (modo fail-closed) | {"error": "rate_limiter_unavailable"} |
409 de campo personalizado em uso | {"error": "field_has_values"} |
Os endpoints de OAuth (/oauth/token, /oauth/revoke, /oauth/introspect) seguem o formato de erro do OAuth 2 (RFC 6749), com error em código curto como rate_limited; veja Autenticação.
O texto de error não é um código estável
O valor de error é uma de duas coisas: um slug (board_not_found, name_required, field_has_values) ou uma frase curta em inglês (task not found, missing team membership, invalid body). Slugs tendem a ser estáveis; frases podem ser reescritas sem aviso. Por isso:
- ramifique por status HTTP (e, no máximo, por slugs que a referência cite);
- nunca faça
error.includes("not found")para decidir fluxo; - mostre ao usuário uma mensagem sua, e guarde o
errororiginal no log.
Em alguns handlers mais antigos do serviço principal, uma falha de banco ainda pode chegar como 500 com o texto cru do driver em error. Não dependa disso, não exiba ao usuário e não o interprete: o serviço de workspace já devolve apenas internal error e registra o detalhe no servidor.
Mapa de status
| Status | Significado | Exemplos reais de error | O que fazer |
|---|---|---|---|
400 | Requisição malformada: JSON inválido, id inválido, parâmetro inválido | invalid body, name required, invalid teamId, invalid before, at least one scope required | Corrija a requisição. Não repita igual. |
401 | Sem credencial válida: token ausente, expirado, revogado ou sessão encerrada | unauthorized | Renove ou recrie a credencial. Não repita igual. |
403 | Autenticado, mas sem permissão | missing team membership, insufficient team role: requires admin, scope required: me:read, api key not accepted on this route | Falta papel, escopo ou participação. Repetir não ajuda. |
404 | Recurso (ou rota) não existe, ou não é visível para você | task not found, team not found, board_not_found, no draft | Veja a seção seguinte. |
409 | Conflito com o estado atual | slug already taken, an export is already queued or running for this scope, field_has_values | Resolva o conflito; reler o recurso costuma ser o primeiro passo. |
413 | Corpo grande demais | body too large | Reduza o corpo (ou envie arquivos pelo fluxo de upload). |
422 | JSON válido, mas regra de negócio violada | name_required, invalid_range | Corrija o dado. Não repita igual. |
429 | Limite de requisições | rate_limited | Espere Retry-After. Veja Limites de taxa. |
500 | Falha interna | internal error | Pode repetir com backoff, só se a operação for segura (abaixo). |
503 | Dependência indisponível ou limitador em fail-closed | rate_limiter_unavailable, guarda de idempotência indisponível; tente de novo | Repita com backoff; respeite Retry-After quando houver. |
Dois detalhes que costumam confundir. Primeiro, o serviço de calendário às vezes escreve a mensagem em português (cursor inválido), porque o texto humano varia por serviço. Segundo, autorização negada nem sempre é 403: para não revelar a existência de recursos de outras equipes, várias rotas respondem 404 quando o recurso existe mas pertence a outro tenant.
O 404 de recurso singular e o 404 de rota
Um 404 tem dois significados que o cliente precisa separar.
404 por contrato. Algumas rotas que devolvem um recurso por escopo respondem 404 com um slug quando o recurso simplesmente ainda não foi criado. É parte do desenho, não uma falha. Dois casos reais:
- GET
/teams/{teamId}/timelines/{timelineId}/boardresponde404com{"error": "board_not_found"}quando o canal ainda não tem quadro. O fluxo certo é oferecer “criar quadro” e chamar oPOSTdo mesmo caminho, que é idempotente: devolve201ao criar e200com o quadro existente se ele já estava lá. - GET
/teams/{teamId}/timelines/{timelineId}/draftresponde404com{"error": "no draft"}quando não há rascunho salvo para aquele canal (eparent_post_id). Trate como “vazio”, não como erro.
404 de rota. Um caminho que a API não conhece (erro de digitação, versão errada, serviço errado) também dá 404. Nos serviços principal e de workspace não há tratador de 404 customizado: a resposta vem do roteador, normalmente em texto simples (404 page not found), sem JSON.
Na prática, o cliente deve tratar assim:
const res = await fetch(url, { headers });
if (res.status === 404) {
const type = res.headers.get('content-type') ?? '';
if (!type.includes('application/json')) {
throw new Error(`rota inexistente: ${url}`); // bug seu, não repita
}
const { error } = await res.json();
if (error === 'board_not_found') return null; // recurso singular ainda não criado
throw new Error(`recurso não encontrado: ${error}`);
}
Rastreio: X-Trace-Id
O serviço principal devolve em toda resposta o cabeçalho X-Trace-Id. Se você enviar um X-Trace-Id seu (até 128 caracteres, sem aspas, barra invertida nem quebra de linha), ele é preservado; caso contrário, o servidor gera um UUID. O valor aparece nos logs do servidor e é o que o suporte usa para achar a linha exata de uma falha. Guarde-o junto de qualquer 5xx que você registrar ou reportar.
curl -sS -i https://api.axuz.net/me/summary \
-H "Authorization: Bearer $AXUZ_TOKEN" \
-H "X-Trace-Id: meu-job-2026-10-22-0001" | grep -i '^x-trace-id'
O serviço de workspace correlaciona os erros internos pelo identificador de requisição do roteador, mas não está documentado que ele o devolva em um cabeçalho de resposta; não conte com isso.
Repetir com segurança
Regra geral: repita apenas quando a falha for transitória e a operação for idempotente.
- Sempre seguro repetir:
GET,HEAD,PUTeDELETE(o resultado final é o mesmo; uma segunda repetição deDELETEpode devolver404porque o recurso já saiu, e isso conta como sucesso), nos status429,500,502,503,504e em falha de rede, com backoff exponencial e jitter. - Nunca repita sem pensar:
POSTque cria algo. Se a conexão cair depois de enviar, você não sabe se o servidor processou. Nas rotas que aceitam o cabeçalhoIdempotency-Key, envie uma chave única por operação; a repetição devolve a mesma resposta comIdempotent-Replay: true. A mesma chave com outro corpo dá422, e a mesma chave ainda em processamento dá409. Rotas sem esse suporte devem ser reconciliadas lendo o estado (por exemplo, listando antes de recriar). - Nunca repita:
400,401,403,404,409,413e422. A mesma requisição produzirá a mesma resposta.
import random
import time
RETRY_STATUS = {429, 500, 502, 503, 504}
def backoff(attempt: int, retry_after: float | None) -> float:
"""Espera: respeita Retry-After; senão exponencial com jitter total."""
if retry_after is not None:
return retry_after + random.uniform(0, 0.5)
return random.uniform(0, min(30, 0.5 * 2 ** attempt))
O exemplo completo, com leitura do Retry-After, está em Limites de taxa.