Saltar al contenido principal

Errores

Todos los errores de la API de Ozzie siguen un formato consistente. Cuando una solicitud falla, el cuerpo de la respuesta siempre contiene un objeto error con un code legible por máquina, un message legible por humanos y un array details opcional con contexto adicional.


Formato de la respuesta de error

{
"error": {
"code": "ERROR_CODE",
"message": "A human-readable description of what went wrong.",
"details": []
}
}
CampoTipoDescripción
error.codestringCódigo de error legible por máquina — úsalo para el manejo programático
error.messagestringDescripción legible del error
error.detailsarrayContexto adicional opcional, como qué campos fallaron la validación

Códigos de error

INVALID_JSON

Estado HTTP400 Bad Request
Cuándo ocurreEl cuerpo de la solicitud no pudo ser analizado como JSON
Cómo resolverVerifica que tu encabezado Content-Type sea application/json y que el cuerpo sea JSON válido. Problemas comunes: comas finales, claves sin comillas, comillas simples en lugar de dobles.
{
"error": {
"code": "INVALID_JSON",
"message": "The request body could not be parsed as JSON.",
"details": []
}
}

VALIDATION_ERROR

Estado HTTP422 Unprocessable Entity
Cuándo ocurreEl cuerpo de la solicitud es JSON válido pero contiene valores de campo inválidos (tipo incorrecto, valor fuera de rango, valor de enum inválido, etc.)
Cómo resolverRevisa el array details — cada entrada identifica el campo específico y la falla de validación.
{
"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

Estado HTTP400 Bad Request
Cuándo ocurreEl código OTP enviado a POST /v1/auth/otp/verify no coincide con el hash almacenado, o el código ya fue utilizado. Los códigos son de uso único — una verificación exitosa invalida el código de inmediato.
Cómo resolverPide al usuario que verifique el código e intente de nuevo. Si el código ya fue utilizado, solicita uno nuevo mediante POST /v1/auth/otp/request.
{
"error": {
"code": "INVALID_CODE",
"message": "The OTP code is incorrect or has already been used.",
"details": []
}
}

CODE_EXPIRED

Estado HTTP400 Bad Request
Cuándo ocurreEl código OTP enviado a POST /v1/auth/otp/verify fue emitido hace más de 10 minutos y ha expirado.
Cómo resolverSolicita un nuevo código mediante POST /v1/auth/otp/request e indica al usuario que lo ingrese rápidamente.
{
"error": {
"code": "CODE_EXPIRED",
"message": "The OTP code has expired. Please request a new code.",
"details": []
}
}

UNAUTHORIZED

Estado HTTP401 Unauthorized
Cuándo ocurreEl encabezado Authorization está ausente, malformado o contiene credenciales inválidas
Cómo resolverVerifica que tu token esté correctamente codificado como base64(client_id:client_secret). Comprueba que no hayas rotado tu secreto sin actualizar las variables de entorno. Asegúrate de que el formato del encabezado sea exactamente Authorization: Bearer <token>.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing authorization credentials.",
"details": []
}
}

FORBIDDEN

Estado HTTP403 Forbidden
Cuándo ocurreTus credenciales son válidas, pero intentas acceder a un recurso que pertenece al usuario de otro cliente
Cómo resolverVerifica que el user_id en la ruta pertenezca a un usuario que creaste bajo tu client_id. No puedes acceder a usuarios creados por otros clientes de API.
{
"error": {
"code": "FORBIDDEN",
"message": "You do not have permission to access this resource.",
"details": []
}
}

NOT_FOUND

Estado HTTP404 Not Found
Cuándo ocurreEl recurso solicitado no existe. Causas comunes: un ID de usuario que no existe, una meta que aún no fue definida, un ID de movimiento de dinero inválido.
Cómo resolverVerifica que el ID en la ruta de tu URL sea correcto y que el recurso haya sido creado antes de intentar recuperarlo.
{
"error": {
"code": "NOT_FOUND",
"message": "The requested resource could not be found.",
"details": [
{
"resource": "user",
"id": "usr_invalid_id"
}
]
}
}

CONFLICT

Estado HTTP409 Conflict
Cuándo ocurreLa solicitud entra en conflicto con el estado actual de un recurso. Más comúnmente: intentar crear un usuario con un external_user_id que ya existe bajo tu cliente.
Cómo resolverSi estás creando un usuario, usa GET /users?external_user_id=... para verificar si ya existe antes de crear. Alternativamente, trátalo como una operación sin cambios y usa el ID de usuario 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

Estado HTTP422 Unprocessable Entity
Cuándo ocurreLlamaste a un endpoint que requiere que exista un intake financiero (ej., POST /plan, POST /chat/messages), pero no se ha enviado ningún intake para este usuario.
Cómo resolverLlama primero a POST /users/{user_id}/financial-intake. El intake captura los ingresos, gastos y meta del usuario, que es la base para todas las funcionalidades posteriores.
{
"error": {
"code": "INTAKE_REQUIRED",
"message": "This user does not have a financial intake. Submit an intake before calling this endpoint.",
"details": []
}
}

PLAN_REQUIRED

Estado HTTP422 Unprocessable Entity
Cuándo ocurreLlamaste a un endpoint que requiere que exista un plan (ej., GET /financial-profile), pero no se ha generado ningún plan para este usuario.
Cómo resolverLlama primero a POST /v1/users/{user_id}/plan/generate. Ten en cuenta que la generación del plan requiere un intake financiero — ver 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

Estado HTTP422 Unprocessable Entity
Cuándo ocurreSe llamó a un endpoint que requiere personality_type sin proporcionarlo, o con un valor inválido.
Cómo resolverPuntúa la personalidad del usuario usando POST /v1/personality/score, almacena el resultado y pásalo como personality_type en el cuerpo de la solicitud. 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

Estado HTTP429 Too Many Requests
Cuándo ocurreTu cliente ha enviado más solicitudes de las que permite tu límite de tasa en la ventana actual.
Cómo resolverEspera hasta el tiempo especificado en el encabezado de respuesta X-RateLimit-Reset, luego reintenta. Implementa backoff exponencial si estás alcanzando los límites con regularidad. Contacta commercial@ozzieapp.com si necesitas un límite mayor para tu 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"
}
]
}
}

Encabezados de respuesta cuando se excede el límite de tasa:

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

VERSION_MISMATCH

Estado HTTP400 Bad Request
Cuándo ocurreLa versión de la API en tu URL no corresponde a una versión compatible, o un recurso fue creado bajo una versión de API diferente a la que usas para solicitarlo.
Cómo resolverAsegúrate de que tu URL base incluya /v1/. Si estás migrando entre versiones, revisa el Changelog para ver los cambios incompatibles.
{
"error": {
"code": "VERSION_MISMATCH",
"message": "The requested API version is not supported.",
"details": [
{
"requested_version": "v0",
"supported_versions": ["v1"]
}
]
}
}

INTERNAL_ERROR

Estado HTTP500 Internal Server Error
Cuándo ocurreOcurrió un error inesperado en los servidores de Ozzie. Este es un error de Ozzie, no tuyo.
Cómo resolverReintenta la solicitud después de un breve delay. Estos errores son típicamente transitorios. Si el error persiste por más de unos minutos, revisa la página de estado de Ozzie o contacta al soporte.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again.",
"details": []
}
}

Recomendaciones de estrategia de reintento

No todos los errores deben reintentarse. Usa esta guía para decidir cuándo reintentar y cuándo mostrar el error al usuario o registrarlo para investigación:

Código de error¿Reintentar?Estrategia
INVALID_JSONNoCorrige la solicitud — reintentar no ayudará
VALIDATION_ERRORNoCorrige los campos inválidos y reenvía
INVALID_CODENoPide al usuario que reingrese o solicite un nuevo OTP
CODE_EXPIREDNoSolicita un nuevo código OTP
UNAUTHORIZEDNoCorrige las credenciales — revisa las variables de entorno
FORBIDDENNoEstás accediendo al recurso equivocado
NOT_FOUNDNoEl recurso no existe — verifica los IDs
CONFLICTNoEl recurso ya existe — maneja de forma idempotente
INTAKE_REQUIREDNoRedirige al usuario a completar el intake primero
PLAN_REQUIREDNoLlama primero a POST /plan
PERSONALITY_REQUIREDNoCorrige el cuerpo de la solicitud
RATE_LIMIT_EXCEEDEDEspera X-RateLimit-Reset, luego reintenta
VERSION_MISMATCHNoCorrige la URL
INTERNAL_ERRORReintenta con backoff exponencial

Ejemplo 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));
}
}
}