Pular para o conteúdo principal

Erros

Todos os erros da API Ozzie seguem um formato consistente. Quando uma requisição falha, o corpo da resposta sempre contém um objeto error com um code legível por máquina, uma message legível por humano e um array details opcional com contexto adicional.


Formato da resposta de erro

{
"error": {
"code": "ERROR_CODE",
"message": "Uma descrição legível do que deu errado.",
"details": []
}
}
CampoTipoDescrição
error.codestringCódigo de erro legível por máquina — use para tratamento programático
error.messagestringDescrição legível do erro
error.detailsarrayContexto adicional opcional, como quais campos falharam na validação

Códigos de erro

INVALID_JSON

Status HTTP400 Bad Request
Quando ocorreO corpo da requisição não pôde ser analisado como JSON
Como resolverVerifique se o header Content-Type é application/json e que o corpo é JSON válido. Problemas comuns: vírgulas no final, chaves sem aspas, aspas simples ao invés de duplas.
{
"error": {
"code": "INVALID_JSON",
"message": "The request body could not be parsed as JSON.",
"details": []
}
}

VALIDATION_ERROR

Status HTTP422 Unprocessable Entity
Quando ocorreO corpo da requisição é JSON válido mas contém valores de campo inválidos (tipo errado, valor fora do intervalo, valor de enum inválido, etc.)
Como resolverVerifique o array details — cada entrada identifica o campo específico e a falha de validação.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "One or more fields failed validation.",
"details": [
{
"field": "cadence",
"message": "Must be one of: weekly, biweekly, twice_monthly, monthly"
},
{
"field": "target_amount",
"message": "Must be a positive number"
}
]
}
}

INVALID_CODE

Status HTTP400 Bad Request
Quando ocorreO código OTP enviado para POST /v1/auth/otp/verify não corresponde ao hash armazenado, ou o código já foi utilizado. Os códigos são de uso único — uma verificação bem-sucedida invalida o código imediatamente.
Como resolverPeça ao usuário que verifique o código e tente novamente. Se o código já foi utilizado, solicite um novo via POST /v1/auth/otp/request.
{
"error": {
"code": "INVALID_CODE",
"message": "The OTP code is incorrect or has already been used.",
"details": []
}
}

CODE_EXPIRED

Status HTTP400 Bad Request
Quando ocorreO código OTP enviado para POST /v1/auth/otp/verify foi emitido há mais de 10 minutos e expirou.
Como resolverSolicite um novo código via POST /v1/auth/otp/request e oriente o usuário a digitá-lo prontamente.
{
"error": {
"code": "CODE_EXPIRED",
"message": "The OTP code has expired. Please request a new code.",
"details": []
}
}

UNAUTHORIZED

Status HTTP401 Unauthorized
Quando ocorreO header Authorization está ausente, malformado ou contém credenciais inválidas
Como resolverVerifique se seu token está corretamente codificado como base64(client_id:client_secret). Confirme que você não rotacionou seu secret sem atualizar as variáveis de ambiente. Certifique-se de que o formato do header é exatamente Authorization: Bearer <token>.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing authorization credentials.",
"details": []
}
}

FORBIDDEN

Status HTTP403 Forbidden
Quando ocorreSuas credenciais são válidas, mas você está tentando acessar um recurso que pertence ao usuário de outro cliente
Como resolverVerifique se o user_id no caminho pertence a um usuário que você criou sob seu client_id. Você não pode acessar usuários criados por outros clientes de API.
{
"error": {
"code": "FORBIDDEN",
"message": "You do not have permission to access this resource.",
"details": []
}
}

NOT_FOUND

Status HTTP404 Not Found
Quando ocorreO recurso solicitado não existe. Causas comuns: um ID de usuário que não existe, uma meta que ainda não foi definida, um ID de movimento financeiro inválido.
Como resolverVerifique se o ID na URL está correto e que o recurso foi criado antes de tentar recuperá-lo.
{
"error": {
"code": "NOT_FOUND",
"message": "The requested resource could not be found.",
"details": [
{
"resource": "user",
"id": "usr_invalid_id"
}
]
}
}

CONFLICT

Status HTTP409 Conflict
Quando ocorreA requisição conflita com o estado atual de um recurso. Mais comumente: tentar criar um usuário com um external_user_id que já existe sob seu cliente.
Como resolverSe você estiver criando um usuário, use GET /users?external_user_id=... para verificar se já existe antes de criar. Alternativamente, trate isso como uma operação sem efeito (no-op) e use o ID do usuário existente.
{
"error": {
"code": "CONFLICT",
"message": "A user with this external_user_id already exists.",
"details": [
{
"field": "external_user_id",
"existing_id": "usr_4f8a1b2c3d"
}
]
}
}

INTAKE_REQUIRED

Status HTTP422 Unprocessable Entity
Quando ocorreVocê chamou um endpoint que requer um intake financeiro (ex.: POST /plan, POST /chat/messages), mas nenhum intake foi enviado para este usuário.
Como resolverChame POST /users/{user_id}/financial-intake primeiro. O intake captura a renda, despesas e meta do usuário, que é a base para todos os recursos posteriores.
{
"error": {
"code": "INTAKE_REQUIRED",
"message": "This user does not have a financial intake. Submit an intake before calling this endpoint.",
"details": []
}
}

PLAN_REQUIRED

Status HTTP422 Unprocessable Entity
Quando ocorreVocê chamou um endpoint que requer um plano (ex.: GET /financial-profile), mas nenhum plano foi gerado para este usuário.
Como resolverChame POST /v1/users/{user_id}/plan/generate primeiro. Observe que a geração de plano em si requer um intake financeiro — veja INTAKE_REQUIRED.
{
"error": {
"code": "PLAN_REQUIRED",
"message": "This user does not have a financial plan. Generate a plan before calling this endpoint.",
"details": []
}
}

PERSONALITY_REQUIRED

Status HTTP422 Unprocessable Entity
Quando ocorreUm endpoint que requer personality_type foi chamado sem ele, ou com um valor inválido.
Como resolverPontue a personalidade do usuário usando POST /v1/personality/score, armazene o resultado e passe-o como personality_type no corpo da requisição. Valores válidos: HOTSHOT, STOCKPILER, GIVER, INDEPENDENT.
{
"error": {
"code": "PERSONALITY_REQUIRED",
"message": "The personality_type field is required for this endpoint.",
"details": []
}
}

RATE_LIMIT_EXCEEDED

Status HTTP429 Too Many Requests
Quando ocorreSeu cliente enviou mais requisições do que o limite de taxa permite na janela atual.
Como resolverAguarde até o tempo especificado no header X-RateLimit-Reset, depois tente novamente. Implemente backoff exponencial se estiver atingindo os limites regularmente. Entre em contato com commercial@ozzieapp.com se precisar de um limite maior para o seu tier.
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "You have exceeded the rate limit for this window. Please retry after the reset time.",
"details": [
{
"reset_at": 1746489600,
"limit": 1000,
"window": "1 minute"
}
]
}
}

Headers de resposta quando o limite é atingido:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1746489600
Retry-After: 47

VERSION_MISMATCH

Status HTTP400 Bad Request
Quando ocorreA versão da API na sua URL não corresponde a uma versão suportada, ou um recurso foi criado em uma versão diferente da que você está usando para acessá-lo.
Como resolverVerifique se sua URL base inclui /v1/. Se estiver migrando entre versões, consulte o Changelog para verificar mudanças incompatíveis.
{
"error": {
"code": "VERSION_MISMATCH",
"message": "The requested API version is not supported.",
"details": [
{
"requested_version": "v0",
"supported_versions": ["v1"]
}
]
}
}

INTERNAL_ERROR

Status HTTP500 Internal Server Error
Quando ocorreOcorreu um erro inesperado nos servidores do Ozzie. Esse é um problema do Ozzie, não seu.
Como resolverTente novamente após um breve delay. Esses erros geralmente são transitórios. Se o erro persistir por mais de alguns minutos, verifique a página de status do Ozzie ou entre em contato com o suporte.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again.",
"details": []
}
}

Recomendações de estratégia de retentativa

Nem todos os erros devem ser retentados. Use este guia para decidir quando tentar novamente e quando expor o erro ao usuário ou registrá-lo para investigação:

Código de erroTentar novamente?Estratégia
INVALID_JSONNãoCorrija a requisição — tentar novamente não ajuda
VALIDATION_ERRORNãoCorrija os campos inválidos e reenvie
INVALID_CODENãoPeça ao usuário para digitar novamente ou solicite um novo OTP
CODE_EXPIREDNãoSolicite um novo código OTP
UNAUTHORIZEDNãoCorrija as credenciais — verifique as variáveis de ambiente
FORBIDDENNãoVocê está acessando o recurso errado
NOT_FOUNDNãoO recurso não existe — verifique os IDs
CONFLICTNãoO recurso já existe — trate de forma idempotente
INTAKE_REQUIREDNãoRedirecione o usuário para completar o intake primeiro
PLAN_REQUIREDNãoChame POST /plan primeiro
PERSONALITY_REQUIREDNãoCorrija o corpo da requisição
RATE_LIMIT_EXCEEDEDSimAguarde X-RateLimit-Reset, depois tente novamente
VERSION_MISMATCHNãoCorrija a URL
INTERNAL_ERRORSimTente novamente com backoff exponencial

Exemplo de backoff exponencial

async function retryableRequest(method, path, body, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await ozzieRequest(method, path, body);
} catch (err) {
const isRetryable =
err.message.includes('RATE_LIMIT_EXCEEDED') ||
err.message.includes('INTERNAL_ERROR');

if (!isRetryable || attempt === maxRetries) throw err;

const delay = Math.min(1000 * Math.pow(2, attempt), 30000); // Max 30s
console.warn(`Retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(r => setTimeout(r, delay));
}
}
}