Transacciones
Las transacciones son el flujo de datos central que alimenta el análisis de gastos, el seguimiento de planes y las recomendaciones personalizadas de Ozzie. Ozzie usa GPT-4o para analizar descripciones en lenguaje natural, imágenes de recibos (OCR), estados de cuenta bancarios en PDF y hojas de cálculo CSV en registros de transacciones estructurados y categorizados.
Un único envío puede resultar en múltiples transacciones — por ejemplo, un mensaje de texto que diga "café $5 y almuerzo $15" producirá dos objetos de transacción separados.
El Objeto Transaction
{
"id": "ozz_txn_01HX9Q5NRWBF3KMZP4VC7YDLA",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"amount_cents": 4500,
"currency": "BRL",
"category": "food",
"envelope": "Dia a Dia",
"description": "Compras no Supermercado Extra",
"transaction_date": "2025-05-05",
"source": "api_text",
"ai_confidence": 0.97,
"created_at": "2025-05-05T14:34:00Z"
}
Campos del Objeto Transaction
| Campo | Tipo | Descripción |
|---|---|---|
id | string | UUID generado por Ozzie para esta transacción |
user_id | string | El usuario de Ozzie al que pertenece esta transacción |
amount_cents | integer | Monto de la transacción en centavos (ej.: 4500 = $45,00) |
currency | string | Código de moneda ISO 4217 (ej.: "BRL", "USD", "EUR") |
category | string | Categoría de gasto (ver tabla a continuación) |
envelope | string | Sobre de presupuesto al que pertenece esta transacción (ver tabla a continuación) |
description | string | Descripción legible generada por IA de la transacción |
transaction_date | string | Fecha de la transacción en formato YYYY-MM-DD |
source | string | Cómo fue enviada la transacción (ver tabla a continuación) |
ai_confidence | float | Puntaje de confianza del análisis de IA de 0.0 a 1.0 |
created_at | string | Timestamp UTC ISO 8601 de cuándo se creó el registro en Ozzie |
Valores de category
| Valor | Descripción |
|---|---|
food | Supermercado, restaurantes, cafeterías, entrega de comida |
transport | Gasolina, transporte público, aplicaciones de transporte, estacionamiento, peajes |
housing | Alquiler, hipoteca, seguro de hogar, reformas |
utilities | Electricidad, agua, internet, factura de teléfono |
health | Farmacia, consultas médicas, gimnasio, seguro médico |
entertainment | Streaming, cine, videojuegos, eventos |
education | Matrículas, libros, cursos, suscripciones educativas |
clothing | Ropa, calzado, accesorios |
income | Salario, pago freelance, ingresos adicionales, reembolso |
other | Cualquier cosa que no encaje en las categorías anteriores |
Valores de envelope
Ozzie agrupa cada transacción en uno de los seis sobres de presupuesto. El sobre se deriva automáticamente de la categoría y siempre está presente en la respuesta.
| Envelope | Categorías incluidas |
|---|---|
Contas | housing, utilities — facturas fijas mensuales |
Dia a Dia | food, transport — gastos cotidianos |
Saúde & Bem-estar | health — salud, farmacia, gimnasio |
Estilo de Vida | entertainment, education, clothing — estilo de vida |
Pessoas | other — regalos, familia, eventos sociales |
Seu Futuro | income — salario, ingresos adicionales, ahorros |
Usa el campo envelope para construir resúmenes de presupuesto agrupados por área de gasto sin ningún mapeo adicional del lado del cliente.
Valores de source
| Valor | Descripción |
|---|---|
whatsapp_text | Mensaje de texto recibido vía WhatsApp |
whatsapp_image | Imagen (recibo/captura de pantalla) recibida vía WhatsApp |
api_text | Texto enviado vía API REST |
api_image | Imagen enviada vía API REST (base64) |
api_pdf | Texto extraído de PDF enviado vía API REST |
api_spreadsheet | Datos CSV enviados vía API REST |
POST /v1/users/{user_id}/transactions
Envía una o más transacciones para su análisis. Ozzie usa GPT-4o para extraer datos estructurados de transacciones del contenido proporcionado. El campo type determina cómo se interpreta el contenido.
Parámetros de Ruta
| Parámetro | Descripción |
|---|---|
user_id | El UUID de Ozzie del usuario o external:{external_user_id} |
Cuerpo de la Solicitud — Unión Discriminada por type
La forma del cuerpo de la solicitud depende del campo type. Todos los tipos comparten el campo language.
type: "text"
Envía una descripción en lenguaje natural de una o más transacciones.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type | string | Sí | Debe ser "text" |
content | string | Sí | Descripción de transacción en lenguaje natural |
language | string | No | Indicación de idioma para el análisis: "en" | "pt" | "es". Por defecto: idioma del usuario. |
{
"type": "text",
"content": "Gastei R$45 no supermercado e R$12 no metrô",
"language": "pt"
}
type: "image"
Envía un recibo, captura de pantalla o foto de una compra como imagen codificada en base64. Ozzie realiza OCR y analiza el resultado.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type | string | Sí | Debe ser "image" |
content | string | Sí | Datos de imagen codificados en base64 |
mime_type | string | Sí | Tipo MIME de la imagen: "image/jpeg" | "image/png" | "image/webp" |
language | string | No | Indicación de idioma para OCR y análisis |
{
"type": "image",
"content": "/9j/4AAQSkZJRgABAQEASABIAAD...",
"mime_type": "image/jpeg",
"language": "pt"
}
type: "pdf"
Envía el contenido de texto extraído de un estado de cuenta bancario o recibo en PDF. Extrae el texto del PDF de tu lado antes de enviarlo.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type | string | Sí | Debe ser "pdf" |
content | string | Sí | Texto plano extraído del PDF |
language | string | No | Indicación de idioma para el análisis |
{
"type": "pdf",
"content": "EXTRATO BANCÁRIO\nData: 2025-05-01\nEstabelecimento: Amazon.com.br\nValor: -R$89,99\nData: 2025-05-02\nEstabelecimento: Netflix\nValor: -R$39,90",
"language": "pt"
}
type: "spreadsheet"
Envía datos de transacciones en formato CSV. Incluye una línea de encabezado. Ozzie normaliza los nombres de columnas de forma flexible (ej.: Data / date / transaction_date son todos reconocidos).
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type | string | Sí | Debe ser "spreadsheet" |
content | string | Sí | String CSV con línea de encabezado |
language | string | No | Indicación de idioma para el análisis |
{
"type": "spreadsheet",
"content": "data,descricao,valor\n2025-05-01,Supermercado Extra,45.00\n2025-05-02,Metrô,12.00\n2025-05-03,Netflix,39.90",
"language": "pt"
}
Respuesta
Retorna un objeto transaction_list que contiene todas las transacciones analizadas del envío. Un único envío puede producir una o muchas transacciones.
{
"object": "transaction_list",
"data": {
"transactions": [ ... ]
}
}
Errores
| Código | HTTP | Cuándo |
|---|---|---|
NOT_FOUND | 404 | El user_id no existe |
VALIDATION_ERROR | 400 | Campos requeridos ausentes, type inválido o mime_type no soportado |
UNAUTHORIZED | 401 | Credenciales ausentes o inválidas |
Ejemplos — Entrada de Texto
- curl
- Node.js
- Python
curl -X POST \
https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/transactions \
-H "Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=" \
-H "Content-Type: application/json" \
-d '{
"type": "text",
"content": "Gastei R$45 no supermercado e R$12 no metrô",
"language": "pt"
}'
import fetch from 'node-fetch';
const token = Buffer.from('seu_client_id:seu_client_secret').toString('base64');
const userId = 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE';
const response = await fetch(
`https://api.ozzieapp.com/v1/users/${userId}/transactions`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'text',
content: 'Gastei R$45 no supermercado e R$12 no metrô',
language: 'pt',
}),
}
);
const { data } = await response.json();
console.log(`Analizadas ${data.transactions.length} transacciones:`);
data.transactions.forEach(t => {
console.log(` • ${t.description}: R$${(t.amount_cents / 100).toFixed(2)} [${t.category}]`);
});
import requests
import base64
token = base64.b64encode(b'seu_client_id:seu_client_secret').decode()
user_id = 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE'
response = requests.post(
f'https://api.ozzieapp.com/v1/users/{user_id}/transactions',
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
},
json={
'type': 'text',
'content': 'Gastei R$45 no supermercado e R$12 no metrô',
'language': 'pt',
}
)
data = response.json()['data']
print(f"Analizadas {len(data['transactions'])} transacciones:")
for t in data['transactions']:
print(f" • {t['description']}: R${t['amount_cents'] / 100:.2f} [{t['category']}]")
Ejemplo de Respuesta — 2 transacciones analizadas de un mensaje de texto (201 Created):
{
"object": "transaction_list",
"data": {
"transactions": [
{
"id": "ozz_txn_01HX9Q5NRWBF3KMZP4VC7YDLA",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"amount_cents": 4500,
"currency": "BRL",
"category": "food",
"description": "Compras no Supermercado Extra",
"transaction_date": "2025-05-05",
"source": "api_text",
"ai_confidence": 0.97,
"created_at": "2025-05-05T14:34:00Z"
},
{
"id": "ozz_txn_01HX9Q5NRWBF3KMZP4VC7YDLB",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"amount_cents": 1200,
"currency": "BRL",
"category": "transport",
"description": "Passagem de metrô",
"transaction_date": "2025-05-05",
"source": "api_text",
"ai_confidence": 0.95,
"created_at": "2025-05-05T14:34:00Z"
}
]
}
}
Ozzie analiza intencionalmente todas las transacciones distintas de una única entrada. Una ida al supermercado más una parada en la cafetería en el mismo mensaje producirá dos registros de transacción separados. Esto corresponde al uso real donde los usuarios describen su día en un único mensaje.
Para imágenes de recibo, JPEG y PNG funcionan bien. Asegúrate de que la imagen sea menor de 10MB antes de la codificación en base64. Las imágenes muy borrosas o rotadas pueden resultar en puntajes de ai_confidence más bajos (por debajo de 0.7). Considera pedirle al usuario que tome la foto nuevamente si la confianza es baja.
GET /v1/users/{user_id}/transactions
Retorna una lista paginada de transacciones de un usuario, ordenada por transaction_date descendente (más reciente primero).
Parámetros de Ruta
| Parámetro | Descripción |
|---|---|
user_id | El UUID de Ozzie del usuario o external:{external_user_id} |
Parámetros de Query
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
limit | integer | 50 | Número de transacciones por página. Máximo 200. |
cursor | string | — | Cursor datetime ISO 8601 para paginación. Retorna transacciones más antiguas que este timestamp. |
from | string | — | Filtro: incluir transacciones a partir de esta fecha (YYYY-MM-DD). |
to | string | — | Filtro: incluir transacciones hasta esta fecha (YYYY-MM-DD). |
category | string | — | Filtrar por categoría (ej.: food, transport). Separa por coma para múltiples: food,transport. |
Respuesta
{
"object": "list",
"data": {
"transactions": [ ... ],
"has_more": true,
"next_cursor": "2025-04-28T09:15:00Z",
"total_count": 142
}
}
Errores
| Código | HTTP | Cuándo |
|---|---|---|
NOT_FOUND | 404 | El user_id no existe |
VALIDATION_ERROR | 400 | Formato de fecha from/to inválido, o limit fuera del rango |
UNAUTHORIZED | 401 | Credenciales ausentes o inválidas |
Ejemplos
- curl
- Node.js
- Python
# Transacciones de mayo de 2025
curl "https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/transactions?from=2025-05-01&to=2025-05-31&limit=100" \
-H "Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ="
# Página siguiente usando cursor
curl "https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/transactions?from=2025-05-01&to=2025-05-31&limit=100&cursor=2025-05-20T10:00:00Z" \
-H "Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ="
import fetch from 'node-fetch';
const token = Buffer.from('seu_client_id:seu_client_secret').toString('base64');
const userId = 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE';
async function fetchMayTransactions() {
const allTransactions = [];
let cursor = null;
do {
const params = new URLSearchParams({
from: '2025-05-01',
to: '2025-05-31',
limit: '100',
});
if (cursor) params.set('cursor', cursor);
const res = await fetch(
`https://api.ozzieapp.com/v1/users/${userId}/transactions?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const json = await res.json();
allTransactions.push(...json.data.transactions);
cursor = json.data.has_more ? json.data.next_cursor : null;
} while (cursor);
return allTransactions;
}
const transactions = await fetchMayTransactions();
console.log(`Total de transacciones en mayo: ${transactions.length}`);
const byCategory = transactions.reduce((acc, t) => {
acc[t.category] = (acc[t.category] || 0) + t.amount_cents;
return acc;
}, {});
Object.entries(byCategory)
.sort(([, a], [, b]) => b - a)
.forEach(([cat, cents]) => {
console.log(` ${cat}: R$${(cents / 100).toFixed(2)}`);
});
import requests
import base64
token = base64.b64encode(b'seu_client_id:seu_client_secret').decode()
user_id = 'ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE'
headers = {'Authorization': f'Bearer {token}'}
all_transactions = []
cursor = None
while True:
params = {'from': '2025-05-01', 'to': '2025-05-31', 'limit': 100}
if cursor:
params['cursor'] = cursor
response = requests.get(
f'https://api.ozzieapp.com/v1/users/{user_id}/transactions',
headers=headers,
params=params
)
data = response.json()['data']
all_transactions.extend(data['transactions'])
if data['has_more']:
cursor = data['next_cursor']
else:
break
print(f"Total de transacciones en mayo: {len(all_transactions)}")
by_category = {}
for t in all_transactions:
by_category[t['category']] = by_category.get(t['category'], 0) + t['amount_cents']
for cat, cents in sorted(by_category.items(), key=lambda x: -x[1]):
print(f" {cat}: R${cents / 100:.2f}")
amount_cents es siempre un entero en la unidad monetaria mínima (centavos para BRL/USD, etc.). Divide entre 100 para mostrar los montos en la moneda correspondiente. Esto evita problemas de precisión de punto flotante.
ai_confidenceLos puntajes por encima de 0.85 indican alta confianza en la categoría y el monto analizados. Los puntajes entre 0.6 y 0.85 pueden beneficiarse de confirmación del usuario. Los puntajes por debajo de 0.6 sugieren que la entrada fue ambigua — considera pedirle al usuario que aclare. Ozzie nunca descarta silenciosamente transacciones de baja confianza; siempre se retornan.