Transações
Transações são o fluxo de dados central que alimenta a análise de gastos, rastreamento de planos e recomendações personalizadas do Ozzie. O Ozzie usa GPT-4o para analisar descrições em linguagem natural, imagens de recibos (OCR), extratos bancários em PDF e planilhas CSV em registros de transações estruturados e categorizados.
Um único envio pode resultar em múltiplas transações — por exemplo, uma mensagem de texto dizendo "café R$5 e almoço R$15" produzirá dois objetos de transação separados.
O Objeto Transaction
{
"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"
}
Campos do Objeto Transaction
| Campo | Tipo | Descrição |
|---|---|---|
id | string | UUID gerado pelo Ozzie para esta transação |
user_id | string | O usuário Ozzie a quem esta transação pertence |
amount_cents | integer | Valor da transação em centavos (ex.: 4500 = R$45,00) |
currency | string | Código de moeda ISO 4217 (ex.: "BRL", "USD", "EUR") |
category | string | Categoria de gasto (veja tabela abaixo) |
description | string | Descrição legível gerada por IA da transação |
transaction_date | string | Data da transação no formato YYYY-MM-DD |
source | string | Como a transação foi enviada (veja tabela abaixo) |
ai_confidence | float | Pontuação de confiança do parsing de IA de 0.0 a 1.0 |
created_at | string | Timestamp UTC ISO 8601 de quando o registro foi criado no Ozzie |
Valores de category
| Valor | Descrição |
|---|---|
food | Supermercado, restaurantes, cafés, delivery de comida |
transport | Gasolina, transporte público, aplicativos de corrida, estacionamento, pedágios |
housing | Aluguel, hipoteca, seguro residencial, reformas |
utilities | Eletricidade, água, internet, conta de telefone |
health | Farmácia, consultas médicas, academia, plano de saúde |
entertainment | Streaming, cinema, jogos, eventos |
education | Mensalidades, livros, cursos, assinaturas educacionais |
clothing | Roupas, calçados, acessórios |
income | Salário, pagamento freelance, renda extra, reembolso |
other | Qualquer coisa que não se encaixe nas categorias acima |
Valores de source
| Valor | Descrição |
|---|---|
whatsapp_text | Mensagem de texto recebida via WhatsApp |
whatsapp_image | Imagem (recibo/screenshot) recebida via WhatsApp |
api_text | Texto enviado via API REST |
api_image | Imagem enviada via API REST (base64) |
api_pdf | Texto extraído de PDF enviado via API REST |
api_spreadsheet | Dados CSV enviados via API REST |
POST /v1/users/{user_id}/transactions
Envie uma ou mais transações para parsing. O Ozzie usa GPT-4o para extrair dados estruturados de transações do conteúdo fornecido. O campo type determina como o conteúdo é interpretado.
Parâmetros de Caminho
| Parâmetro | Descrição |
|---|---|
user_id | O UUID Ozzie do usuário ou external:{external_user_id} |
Corpo da Requisição — União Discriminada por type
A forma do corpo da requisição depende do campo type. Todos os tipos compartilham o campo language.
type: "text"
Envie uma descrição em linguagem natural de uma ou mais transações.
| Campo | Tipo | Obrigat ório | Descrição |
|---|---|---|---|
type | string | Sim | Deve ser "text" |
content | string | Sim | Descrição de transação em linguagem natural |
language | string | Não | Dica de idioma para parsing: "en" | "pt" | "es". Padrão: idioma do usuário. |
{
"type": "text",
"content": "Gastei R$45 no supermercado e R$12 no metrô",
"language": "pt"
}
type: "image"
Envie um recibo, screenshot ou foto de uma compra como imagem codificada em base64. O Ozzie realiza OCR e analisa o resultado.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | string | Sim | Deve ser "image" |
content | string | Sim | Dados de imagem codificados em base64 |
mime_type | string | Sim | Tipo MIME da imagem: "image/jpeg" | "image/png" | "image/webp" |
language | string | Não | Dica de idioma para OCR e parsing |
{
"type": "image",
"content": "/9j/4AAQSkZJRgABAQEASABIAAD...",
"mime_type": "image/jpeg",
"language": "pt"
}
type: "pdf"
Envie o conteúdo de texto extraído de um extrato bancário ou recibo em PDF. Extraia o texto do PDF do seu lado antes de enviar.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | string | Sim | Deve ser "pdf" |
content | string | Sim | Texto simples extraído do PDF |
language | string | Não | Dica de idioma para parsing |
{
"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"
Envie dados de transação formatados em CSV. Inclua uma linha de cabeçalho. O Ozzie normaliza nomes de colunas de forma flexível (ex.: Data / date / transaction_date são todos reconhecidos).
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | string | Sim | Deve ser "spreadsheet" |
content | string | Sim | String CSV com linha de cabeçalho |
language | string | Não | Dica de idioma para parsing |
{
"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"
}
Resposta
Retorna um objeto transaction_list contendo todas as transações analisadas do envio. Um único envio pode produzir uma ou muitas transações.
{
"object": "transaction_list",
"data": {
"transactions": [ ... ]
}
}
Erros
| Código | HTTP | Quando |
|---|---|---|
NOT_FOUND | 404 | O user_id não existe |
VALIDATION_ERROR | 400 | Campos obrigatórios ausentes, type inválido ou mime_type não suportado |
UNAUTHORIZED | 401 | Credenciais ausentes ou inválidas |
Exemplos — 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(`Analisadas ${data.transactions.length} transações:`);
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"Analisadas {len(data['transactions'])} transações:")
for t in data['transactions']:
print(f" • {t['description']}: R${t['amount_cents'] / 100:.2f} [{t['category']}]")
Exemplo de Resposta — 2 transações analisadas de uma mensagem 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"
}
]
}
}
O Ozzie intencionalmente analisa todas as transações distintas de uma única entrada. Uma ida ao supermercado mais uma parada no café na mesma mensagem produzirá dois registros de transação separados. Isso corresponde ao uso real onde os usuários descrevem seu dia em uma única mensagem.
Para imagens de recibo, JPEG e PNG funcionam bem. Certifique-se de que a imagem seja menor que 10MB antes da codificação em base64. Imagens muito borradas ou rotacionadas podem resultar em pontuações de ai_confidence mais baixas (abaixo de 0.7). Considere pedir ao usuário para tirar a foto novamente se a confiança estiver baixa.
GET /v1/users/{user_id}/transactions
Retorna uma lista paginada de transações de um usuário, ordenada por transaction_date decrescente (mais recente primeiro).
Parâmetros de Caminho
| Parâmetro | Descrição |
|---|---|
user_id | O UUID Ozzie do usuário ou external:{external_user_id} |
Parâmetros de Query
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
limit | integer | 50 | Número de transações por página. Máximo 200. |
cursor | string | — | Cursor datetime ISO 8601 para paginação. Retorna transações mais antigas que este timestamp. |
from | string | — | Filtro: incluir transações a partir desta data (YYYY-MM-DD). |
to | string | — | Filtro: incluir transações até esta data (YYYY-MM-DD). |
category | string | — | Filtrar por categoria (ex.: food, transport). Separe por vírgula para múltiplas: food,transport. |
Resposta
{
"object": "list",
"data": {
"transactions": [ ... ],
"has_more": true,
"next_cursor": "2025-04-28T09:15:00Z",
"total_count": 142
}
}
Erros
| Código | HTTP | Quando |
|---|---|---|
NOT_FOUND | 404 | O user_id não existe |
VALIDATION_ERROR | 400 | Formato de data from/to inválido, ou limit fora do intervalo |
UNAUTHORIZED | 401 | Credenciais ausentes ou inválidas |
Exemplos
- curl
- Node.js
- Python
# Transações de maio 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="
# Próxima página 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 transações em maio: ${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 transações em maio: {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 é sempre um inteiro na menor unidade monetária (centavos para BRL/USD, etc.). Divida por 100 para exibir valores em reais. Isso evita problemas de precisão de ponto flutuante.
ai_confidencePontuações acima de 0.85 indicam alta confiança na categoria e valor analisados. Pontuações entre 0.6 e 0.85 podem se beneficiar de confirmação do usuário. Pontuações abaixo de 0.6 sugerem que a entrada foi ambígua — considere pedir ao usuário que esclareça. O Ozzie nunca descarta silenciosamente transações de baixa confiança; elas são sempre retornadas.