Intake Financiero
El intake financiero es la base de toda experiencia de usuario en Ozzie. Captura un snapshot puntual del panorama financiero mensual de un usuario — ingresos, gastos y objetivo principal. Ozzie usa estos datos para generar un plan financiero personalizado, calcular objetivos de ahorro y proporcionar recomendaciones contextuales.
POST /v1/users/{user_id}/plan/generate devolverá INTAKE_REQUIRED hasta que se haya enviado al menos un snapshot de intake financiero. Siempre envía un intake antes de generar un plan.
El Objeto Financial Intake
{
"object": "financial_intake",
"data": {
"id": "ozz_intake_01HX9M3FQKRNT8YDWP6BZ2LSA",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"monthly_income": 5000,
"monthly_expenses": 3200,
"monthly_surplus": 1800,
"financial_goal": "savings",
"next_pay_date": "2025-05-15",
"created_at": "2025-05-05T14:31:00Z"
}
}
Campos del Objeto Financial Intake
| Campo | Tipo | Descripción |
|---|---|---|
id | string | UUID generado por Ozzie para este snapshot de intake |
user_id | string | El usuario Ozzie al que pertenece este intake |
monthly_income | number | Ingresos mensuales brutos en la moneda base del usuario |
monthly_expenses | number | Total de gastos mensuales |
monthly_surplus | number | Campo calculado: monthly_income - monthly_expenses |
financial_goal | string | "savings" o "debt" — el tipo de objetivo financiero principal del usuario |
next_pay_date | string | null | Fecha del próximo pago del usuario en formato YYYY-MM-DD |
created_at | string | Timestamp UTC ISO 8601 de cuando se creó este snapshot |
Modelo de Snapshot Inmutable
Cada llamada a POST /v1/users/{user_id}/financial-intake crea un nuevo snapshot inmutable. Los snapshots anteriores se conservan en el historial. Ozzie siempre usa el snapshot creado más recientemente para la generación de planes y recomendaciones.
Este diseño significa:
- Nunca necesitas "actualizar" un intake — simplemente envía uno nuevo.
- Los snapshots históricos están disponibles vía
GET /v1/users/{user_id}/financial-intake/historypara auditoría y análisis de tendencias. - Regenerar un plan después de un nuevo envío de intake usará automáticamente los números más recientes.
Intake del 1 mayo: income=4500, expenses=3100 ← usado para el plan de mayo
↓
Intake del 31 mayo: income=5000, expenses=3200 ← se convierte en el nuevo más reciente
↓
POST /plan/generate → usa snapshot del 31 mayo
Envía un intake nuevo cuando el usuario reporte un cambio significativo: aumento de salario, nueva factura recurrente, pago de deuda o evento de vida (nuevo trabajo, cambio de ciudad). El reenvío mensual es un buen cadencia predeterminada.
POST /v1/users/{user_id}/financial-intake
Almacena un nuevo snapshot financiero mensual para el usuario.
Parámetros de Ruta
| Parámetro | Descripción |
|---|---|
user_id | El UUID Ozzie del usuario o external:{external_user_id} |
Cuerpo de la Solicitud
| Campo | Tipo | Requerido | Restricciones | Descripción |
|---|---|---|---|---|
monthly_income | number | Sí | Debe ser ≥ 0 | Ingresos mensuales brutos en la moneda base del usuario |
monthly_expenses | number | Sí | Debe ser ≥ 0 | Total de gastos mensuales (alquiler, comida, facturas, suscripciones, etc.) |
financial_goal | string | Sí | "savings" | "debt" | El objetivo financiero principal del usuario |
next_pay_date | string | No | Fecha ISO YYYY-MM-DD | Fecha del próximo pago del usuario — usada para temporización del flujo de caja |
Valores de financial_goal
| Valor | Significado | Comportamiento del plan |
|---|---|---|
"savings" | La prioridad del usuario es construir ahorros o un fondo de emergencia | El plan enfatiza la tasa de contribución y el crecimiento compuesto |
"debt" | La prioridad del usuario es pagar deudas | El plan enfatiza la secuencia de pago de deudas (avalancha o bola de nieve) |
Respuesta
Devuelve el objeto financial_intake creado con HTTP 201 Created.
Errores
| Código | HTTP | Cuándo |
|---|---|---|
NOT_FOUND | 404 | El user_id no existe |
VALIDATION_ERROR | 400 | Un campo requerido está ausente o tiene un valor inválido |
UNAUTHORIZED | 401 | Credenciales ausentes o inválidas |
Ejemplos
- curl
- Node.js
- Python
curl -X POST \
https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/financial-intake \
-H "Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=" \
-H "Content-Type: application/json" \
-d '{
"monthly_income": 5000,
"monthly_expenses": 3200,
"financial_goal": "savings",
"next_pay_date": "2025-05-15"
}'
import fetch from 'node-fetch';
const token = Buffer.from('tu_client_id:tu_client_secret').toString('base64');
const userId = 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE';
const response = await fetch(
`https://api.ozzieapp.com/v1/users/${userId}/financial-intake`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
monthly_income: 5000,
monthly_expenses: 3200,
financial_goal: 'savings',
next_pay_date: '2025-05-15',
}),
}
);
const { data: intake } = await response.json();
console.log(`Excedente mensual: $${intake.monthly_surplus}`);
import requests
import base64
token = base64.b64encode(b'tu_client_id:tu_client_secret').decode()
user_id = 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE'
response = requests.post(
f'https://api.ozzieapp.com/v1/users/{user_id}/financial-intake',
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
},
json={
'monthly_income': 5000,
'monthly_expenses': 3200,
'financial_goal': 'savings',
'next_pay_date': '2025-05-15',
}
)
intake = response.json()['data']
print(f"Excedente mensual: ${intake['monthly_surplus']}")
Ejemplo de Respuesta (201 Created):
{
"object": "financial_intake",
"data": {
"id": "ozz_intake_01HX9M3FQKRNT8YDWP6BZ2LSA",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"monthly_income": 5000,
"monthly_expenses": 3200,
"monthly_surplus": 1800,
"financial_goal": "savings",
"next_pay_date": "2025-05-15",
"created_at": "2025-05-05T14:31:00Z"
}
}
Respuesta de Error — Campo requerido faltante (400):
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Field 'financial_goal' is required. Must be one of: 'savings', 'debt'."
}
}
Respuesta de Error — Valor de ingresos inválido (400):
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Field 'monthly_income' must be a non-negative number. Received: -500."
}
}
GET /v1/users/{user_id}/financial-intake
Devuelve el snapshot de intake financiero creado más recientemente para el usuario.
Parámetros de Ruta
| Parámetro | Descripción |
|---|---|
user_id | El UUID Ozzie del usuario o external:{external_user_id} |
Respuesta
Devuelve el objeto financial_intake con HTTP 200 OK. Si no se ha enviado ningún intake, devuelve HTTP 404 con código NOT_FOUND.
Errores
| Código | HTTP | Cuándo |
|---|---|---|
NOT_FOUND | 404 | El usuario existe pero no ha enviado ningún intake aún, o el user_id es inválido |
UNAUTHORIZED | 401 | Credenciales ausentes o inválidas |
Ejemplos
- curl
- Node.js
- Python
curl https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/financial-intake \
-H "Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ="
import fetch from 'node-fetch';
const token = Buffer.from('tu_client_id:tu_client_secret').toString('base64');
const userId = 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE';
const response = await fetch(
`https://api.ozzieapp.com/v1/users/${userId}/financial-intake`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
if (response.status === 404) {
console.log('Ningún intake enviado aún. Solicita los detalles financieros al usuario.');
} else {
const { data: intake } = await response.json();
console.log(`Intake más reciente de ${intake.created_at}: excedente $${intake.monthly_surplus}`);
}
import requests
import base64
token = base64.b64encode(b'tu_client_id:tu_client_secret').decode()
user_id = 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE'
response = requests.get(
f'https://api.ozzieapp.com/v1/users/{user_id}/financial-intake',
headers={'Authorization': f'Bearer {token}'}
)
if response.status_code == 404:
print('Ningún intake enviado aún.')
else:
intake = response.json()['data']
print(f"Intake más reciente: excedente ${intake['monthly_surplus']}")
Ejemplo de Respuesta (200 OK):
{
"object": "financial_intake",
"data": {
"id": "ozz_intake_01HX9M3FQKRNT8YDWP6BZ2LSA",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"monthly_income": 5000,
"monthly_expenses": 3200,
"monthly_surplus": 1800,
"financial_goal": "savings",
"next_pay_date": "2025-05-15",
"created_at": "2025-05-05T14:31:00Z"
}
}
Respuesta de Error — Sin intake aún (404):
{
"error": {
"code": "NOT_FOUND",
"message": "No financial intake found for user 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE'. Submit a POST /v1/users/{user_id}/financial-intake first."
}
}
Antes de llamar a POST /v1/users/{user_id}/plan/generate, puedes opcionalmente llamar a este endpoint para confirmar que existe un intake y previsualizar el monthly_surplus. Esto evita un error de round-trip si el intake nunca fue enviado.