Money Moves
Los Money Moves son ciclos de acción financiera estructurados y con plazo definido, entregados a los usuarios en la cadencia elegida (semanal, quincenal, etc.). Cada ciclo contiene un conjunto de tareas en tres categorías:
| Tipo de tarea | Descripción |
|---|---|
"do" | Una acción financiera concreta con un monto asociado (ej.: transferir $150 a ahorros) |
"learn" | Una tarea corta de educación financiera adaptada a la situación del usuario |
"mind" | Un ejercicio de mentalidad o reflexión para construir hábitos financieros saludables |
Los Money Moves requieren que exista un plan financiero primero. Usa POST /v1/users/{user_id}/plan antes de llamar a /money-moves/generate.
Referencia del objeto
Objeto Cycle
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador único del ciclo |
user_id | string | El usuario Ozzie al que pertenece este ciclo |
due_date | string (ISO 8601) | La fecha límite para completar las tareas de este ciclo |
cadence | "weekly" | "biweekly" | "twice_monthly" | "monthly" | La frecuencia de la que forma parte este ciclo |
status | "scheduled" | "available" | "completed" | "skipped" | Estado actual del ciclo |
tasks | Task[] | La lista de tareas en este ciclo |
created_at | string (ISO 8601) | Cuando se generó el ciclo |
Objeto Task
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador único de la tarea |
task_type | "do" | "learn" | "mind" | La categoría de la tarea |
title | string | Descripción legible de la tarea |
amount_cents | integer | null | Monto en centavos (solo presente para tareas "do") |
status | "pending" | "done" | "skipped" | Si la tarea ha sido completada |
completed_at | string (ISO 8601) | null | Cuando la tarea fue marcada como completada (null si está pendiente/saltada) |
GET /v1/users/{user_id}/money-moves
Lista todos los ciclos de money move de un usuario. Soporta filtrado por estado y paginación basada en cursor.
Parámetros de ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
user_id | string | Sí | El ID de usuario de Ozzie |
Parámetros de query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
status | "scheduled" | "available" | "completed" | "skipped" | No | Filtrar ciclos por estado |
limit | integer | No | Número de ciclos a devolver. Predeterminado: 10. Máx: 50. |
cursor | string | No | Cursor de paginación del next_cursor de una respuesta anterior |
Solicitud
- curl
- Node.js
- Python
curl "https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves?status=available&limit=5" \
-H "Authorization: Bearer ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu"
const token = Buffer.from(
`${process.env.OZZIE_CLIENT_ID}:${process.env.OZZIE_CLIENT_SECRET}`
).toString('base64');
const params = new URLSearchParams({ status: 'available', limit: '5' });
const response = await fetch(
`https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves?${params}`,
{
headers: { 'Authorization': `Bearer ${token}` },
}
);
const { data, pagination } = await response.json();
console.log(data);
import base64, os, httpx
token = base64.b64encode(
f"{os.environ['OZZIE_CLIENT_ID']}:{os.environ['OZZIE_CLIENT_SECRET']}".encode()
).decode()
response = httpx.get(
"https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves",
params={"status": "available", "limit": 5},
headers={"Authorization": f"Bearer {token}"},
)
result = response.json()
print(result["data"])
Respuesta
{
"object": "list",
"data": [
{
"id": "mmv_3a7c9e1b2d",
"user_id": "usr_4f8a1b2c3d",
"due_date": "2025-05-19T23:59:59Z",
"cadence": "biweekly",
"status": "available",
"created_at": "2025-05-05T14:40:00Z",
"tasks": [
{
"id": "tsk_1a2b3c4d5e",
"task_type": "do",
"title": "Transfiere $150 a tu cuenta de ahorro del Fondo de Emergencia",
"amount_cents": 15000,
"status": "pending",
"completed_at": null
},
{
"id": "tsk_2b3c4d5e6f",
"task_type": "learn",
"title": "Lee: Por qué $3,000 es el primer hito correcto para un fondo de emergencia",
"amount_cents": null,
"status": "pending",
"completed_at": null
},
{
"id": "tsk_3c4d5e6f7g",
"task_type": "mind",
"title": "Escribe una cosa con la que te sentirías menos estresado teniendo $10,000 en ahorros",
"amount_cents": null,
"status": "pending",
"completed_at": null
}
]
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
}
Errores
| Código | Estado HTTP | Cuándo ocurre |
|---|---|---|
UNAUTHORIZED | 401 | Credenciales ausentes o inválidas |
NOT_FOUND | 404 | El usuario no existe |
VALIDATION_ERROR | 422 | Valor de status inválido o limit fuera de rango |
POST /v1/users/{user_id}/money-moves/generate
Activa la generación del próximo ciclo de money move para el usuario. La IA de Ozzie analiza el plan actual, meta e historial de transacciones del usuario para producir un nuevo conjunto de tareas personalizadas.
Este endpoint requiere que ya exista un plan financiero para el usuario. Llama a POST /v1/users/{user_id}/plan primero. Si no existe ningún plan, recibirás un error PLAN_REQUIRED.
Cada llamada genera exactamente un nuevo ciclo. El ciclo se coloca en estado "scheduled" y se vuelve "available" en su due_date.
Parámetros de ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
user_id | string | Sí | El ID de usuario de Ozzie |
Cuerpo de la solicitud
No se requiere cuerpo de solicitud.
Solicitud
- curl
- Node.js
- Python
curl -X POST https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/generate \
-H "Authorization: Bearer ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu" \
-H "Content-Type: application/json"
const token = Buffer.from(
`${process.env.OZZIE_CLIENT_ID}:${process.env.OZZIE_CLIENT_SECRET}`
).toString('base64');
const response = await fetch(
'https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/generate',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
const { data } = await response.json();
console.log(data);
import base64, os, httpx
token = base64.b64encode(
f"{os.environ['OZZIE_CLIENT_ID']}:{os.environ['OZZIE_CLIENT_SECRET']}".encode()
).decode()
response = httpx.post(
"https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/generate",
headers={"Authorization": f"Bearer {token}"},
)
print(response.json()["data"])
Respuesta
Devuelve el ciclo recién generado con todas sus tareas.
{
"object": "money_move",
"data": {
"id": "mmv_5d8f2a4b9c",
"user_id": "usr_4f8a1b2c3d",
"due_date": "2025-06-02T23:59:59Z",
"cadence": "biweekly",
"status": "scheduled",
"created_at": "2025-05-05T14:45:00Z",
"tasks": [
{
"id": "tsk_4d5e6f7g8h",
"task_type": "do",
"title": "Transfiere $150 a tu Fondo de Emergencia — ¡ya estás al 12% del camino!",
"amount_cents": 15000,
"status": "pending",
"completed_at": null
},
{
"id": "tsk_5e6f7g8h9i",
"task_type": "learn",
"title": "Lee: Cómo automatizar tus ahorros para nunca olvidar una transferencia",
"amount_cents": null,
"status": "pending",
"completed_at": null
},
{
"id": "tsk_6f7g8h9i0j",
"task_type": "mind",
"title": "Reflexiona: ¿Cuál es una suscripción que has mantenido pero apenas has usado este mes?",
"amount_cents": null,
"status": "pending",
"completed_at": null
}
]
}
}
Errores
| Código | Estado HTTP | Cuándo ocurre |
|---|---|---|
UNAUTHORIZED | 401 | Credenciales ausentes o inválidas |
NOT_FOUND | 404 | El usuario no existe |
PLAN_REQUIRED | 422 | No existe ningún plan financiero para el usuario |
INTERNAL_ERROR | 500 | La generación de IA falló — seguro para reintentar |
GET /v1/users/{user_id}/money-moves/{move_id}
Recupera un único ciclo de money move por ID, incluyendo todas sus tareas.
Parámetros de ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
user_id | string | Sí | El ID de usuario de Ozzie |
move_id | string | Sí | El ID del ciclo de money move |
Solicitud
- curl
- Node.js
- Python
curl https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/mmv_3a7c9e1b2d \
-H "Authorization: Bearer ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu"
const token = Buffer.from(
`${process.env.OZZIE_CLIENT_ID}:${process.env.OZZIE_CLIENT_SECRET}`
).toString('base64');
const response = await fetch(
'https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/mmv_3a7c9e1b2d',
{
headers: { 'Authorization': `Bearer ${token}` },
}
);
const { data } = await response.json();
console.log(data);
import base64, os, httpx
token = base64.b64encode(
f"{os.environ['OZZIE_CLIENT_ID']}:{os.environ['OZZIE_CLIENT_SECRET']}".encode()
).decode()
response = httpx.get(
"https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/mmv_3a7c9e1b2d",
headers={"Authorization": f"Bearer {token}"},
)
print(response.json()["data"])
Errores
| Código | Estado HTTP | Cuándo ocurre |
|---|---|---|
UNAUTHORIZED | 401 | Credenciales ausentes o inválidas |
NOT_FOUND | 404 | Usuario o ciclo de money move no encontrado |
PATCH /v1/users/{user_id}/money-moves/{move_id}
Actualiza el estado de un ciclo de money move. Úsalo para marcar un ciclo como completado o saltado cuando el usuario termina sus tareas o decide no hacer el ciclo actual.
Parámetros de ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
user_id | string | Sí | El ID de usuario de Ozzie |
move_id | string | Sí | El ID del ciclo de money move |
Cuerpo de la solicitud
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
status | "completed" | "skipped" | Sí | El nuevo estado del ciclo |
Solicitud
- curl
- Node.js
- Python
curl -X PATCH https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/mmv_3a7c9e1b2d \
-H "Authorization: Bearer ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu" \
-H "Content-Type: application/json" \
-d '{
"status": "completed"
}'
const token = Buffer.from(
`${process.env.OZZIE_CLIENT_ID}:${process.env.OZZIE_CLIENT_SECRET}`
).toString('base64');
const response = await fetch(
'https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/mmv_3a7c9e1b2d',
{
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ status: 'completed' }),
}
);
const { data } = await response.json();
console.log(data.status); // "completed"
import base64, os, httpx
token = base64.b64encode(
f"{os.environ['OZZIE_CLIENT_ID']}:{os.environ['OZZIE_CLIENT_SECRET']}".encode()
).decode()
response = httpx.patch(
"https://api.ozzieapp.com/v1/users/usr_4f8a1b2c3d/money-moves/mmv_3a7c9e1b2d",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={"status": "completed"},
)
print(response.json()["data"]["status"]) # "completed"
Respuesta
Devuelve el objeto de ciclo actualizado.
{
"object": "money_move",
"data": {
"id": "mmv_3a7c9e1b2d",
"user_id": "usr_4f8a1b2c3d",
"due_date": "2025-05-19T23:59:59Z",
"cadence": "biweekly",
"status": "completed",
"created_at": "2025-05-05T14:40:00Z",
"tasks": [
{
"id": "tsk_1a2b3c4d5e",
"task_type": "do",
"title": "Transfiere $150 a tu cuenta de ahorro del Fondo de Emergencia",
"amount_cents": 15000,
"status": "done",
"completed_at": "2025-05-06T09:15:00Z"
},
{
"id": "tsk_2b3c4d5e6f",
"task_type": "learn",
"title": "Lee: Por qué $3,000 es el primer hito correcto para un fondo de emergencia",
"amount_cents": null,
"status": "done",
"completed_at": "2025-05-07T20:30:00Z"
},
{
"id": "tsk_3c4d5e6f7g",
"task_type": "mind",
"title": "Escribe una cosa con la que te sentirías menos estresado teniendo $10,000 en ahorros",
"amount_cents": null,
"status": "done",
"completed_at": "2025-05-07T20:35:00Z"
}
]
}
}
Errores
| Código | Estado HTTP | Cuándo ocurre |
|---|---|---|
UNAUTHORIZED | 401 | Credenciales ausentes o inválidas |
NOT_FOUND | 404 | Usuario o ciclo de money move no encontrado |
VALIDATION_ERROR | 422 | Valor de status inválido, o el ciclo ya está en estado terminal |
INVALID_JSON | 400 | El cuerpo de la solicitud no es JSON válido |
Después de marcar un ciclo como "completed" o "skipped", puedes llamar inmediatamente a POST /money-moves/generate para encolar el próximo ciclo. Esto mantiene las listas de tareas de tus usuarios actualizadas sin brechas.