API Flow & Sequence
This page describes the exact sequence of API calls to integrate Ozzie from scratch. It is designed so that any developer or LLM agent can read it and immediately understand the correct order of operations, what each call requires, what it unlocks, and how to recover from errors.
Ozzie supports two flows: Flow A (create user first, then use the Ozzie UUID) and Flow B (skip user creation β use external:{your_id} directly in any endpoint and Ozzie auto-provisions the user on first use). The sequence below shows Flow A. For Flow B, see External ID Integration.
Full Integration Flowβ
ββββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββ
β OZZIE INTEGRATION SEQUENCE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[1] POST /v1/users
β
β Creates the user record. Returns user_id (UUID).
βΌ
[2] POST /v1/users/{user_id}/financial-intake
β
β Stores monthly income, expenses, and financial goal.
β Required before generating a plan.
βΌ
[3] POST /v1/users/{user_id}/plan/generate
β
β Generates a personalized savings/debt plan using
β the latest financial intake snapshot.
βΌ
[4] POST /v1/users/{user_id}/goals
β
β Sets specific financial goals (e.g., "Save $5,000
β for emergency fund by Dec 2025").
βΌ
[5] POST /v1/users/{user_id}/transactions
β
β Submits expenses/incomes (text, image, PDF, CSV).
β Ozzie uses GPT-4o to parse and categorize them.
βΌ
[6] GET /v1/users/{user_id}/money-moves
β
β Retrieves personalized spending recommendations
β based on the plan and recent transactions.
βΌ
[7] POST /v1/users/{user_id}/chat
β
β Send a message to Ozzie's financial assistant.
β Context-aware: uses plan, goals, and transactions.
βΌ
END (repeat steps 5β7 for ongoing usage)
Step-by-Step Referenceβ
Step 1 β Create Userβ
Endpoint: POST /v1/users
Unlocks: All subsequent endpoints for this user.
| Field | Type | Required | Description |
|---|---|---|---|
external_user_id | string | Yes | Your internal user ID (max 100 chars) |
name | string | No | Display name |
email | string | No | Valid email address |
phone | string | No | E.164 format, e.g. +15551234567 |
language | string | No | "en" | "pt" | "es" β defaults to "en" |
Sample Request:
POST https://api.ozzieapp.com/v1/users
Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=
Content-Type: application/json
{
"external_user_id": "usr_8821",
"name": "Maria Santos",
"email": "maria@example.com",
"phone": "+15551234567",
"language": "en"
}
Sample Response:
{
"object": "user",
"data": {
"id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"external_user_id": "usr_8821",
"name": "Maria Santos",
"email": "maria@example.com",
"phone": "+15551234567",
"language": "en",
"onboarding_stage": "intake_pending",
"created_at": "2025-05-05T14:30:00Z"
}
}
Save the id field (ozz_usr_...). This is the user_id used in all subsequent calls.
Step 2 β Send Financial Intakeβ
Endpoint: POST /v1/users/{user_id}/financial-intake
Unlocks: Plan generation (Step 3). The plan endpoint will return INTAKE_REQUIRED until this is called.
| Field | Type | Required | Description |
|---|---|---|---|
monthly_income | number | Yes | Gross monthly income in the user's base currency |
monthly_expenses | number | Yes | Total monthly outgoings |
financial_goal | string | Yes | "savings" | "debt" |
next_pay_date | string | No | ISO date YYYY-MM-DD β user's next paycheck date |
Sample Request:
POST https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/financial-intake
Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=
Content-Type: application/json
{
"monthly_income": 5000,
"monthly_expenses": 3200,
"financial_goal": "savings",
"next_pay_date": "2025-05-15"
}
Sample Response:
{
"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"
}
}
Step 3 β Generate Planβ
Endpoint: POST /v1/users/{user_id}/plan/generate
Requires: Financial intake must exist (Step 2).
Unlocks: Money moves (Step 6), goal-aware chat (Step 7).
Sample Request:
POST https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/plan/generate
Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=
Content-Type: application/json
{}
Sample Response:
{
"object": "plan",
"data": {
"id": "ozz_plan_01HX9N7CQTVR2BXWM5GD8KPNA",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"financial_goal": "savings",
"monthly_surplus": 1800,
"recommended_monthly_savings": 900,
"savings_rate_percent": 18,
"summary": "Based on your $1,800 monthly surplus, Ozzie recommends saving $900/month β a solid 18% savings rate. Focus areas: reduce dining out and subscriptions.",
"status": "active",
"created_at": "2025-05-05T14:32:00Z"
}
}
Step 4 β Set Goalsβ
Endpoint: POST /v1/users/{user_id}/goals
Unlocks: Goal-tracking in chat responses and money moves.
Sample Request:
POST https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/goals
Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=
Content-Type: application/json
{
"name": "Emergency Fund",
"target_amount": 5000,
"currency": "USD",
"target_date": "2025-12-31"
}
Sample Response:
{
"object": "goal",
"data": {
"id": "ozz_goal_01HX9P2MQWKD4FNYR3VC6BJSH",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"name": "Emergency Fund",
"target_amount": 5000,
"current_amount": 0,
"currency": "USD",
"target_date": "2025-12-31",
"monthly_contribution_needed": 625,
"on_track": true,
"created_at": "2025-05-05T14:33:00Z"
}
}
Step 5 β Track Transactionsβ
Endpoint: POST /v1/users/{user_id}/transactions
Note: Can be called any number of times throughout the user's lifecycle.
Supports type: text, image, pdf, spreadsheet. Ozzie uses GPT-4o to parse one or more transactions from each submission.
Sample Request (text):
POST https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/transactions
Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=
Content-Type: application/json
{
"type": "text",
"content": "Spent $45 on groceries at Whole Foods and $12 on the subway",
"language": "en"
}
Sample Response:
{
"object": "transaction_list",
"data": {
"transactions": [
{
"id": "ozz_txn_01HX9Q5NRWBF3KMZP4VC7YDLA",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"amount_cents": 4500,
"currency": "USD",
"category": "food",
"description": "Groceries at Whole Foods",
"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": "USD",
"category": "transport",
"description": "Subway fare",
"transaction_date": "2025-05-05",
"source": "api_text",
"ai_confidence": 0.95,
"created_at": "2025-05-05T14:34:00Z"
}
]
}
}
Step 6 β Get Money Movesβ
Endpoint: GET /v1/users/{user_id}/money-moves
Requires: Plan must exist (Step 3).
Sample Request:
GET https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/money-moves
Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=
Sample Response:
{
"object": "money_move_list",
"data": {
"moves": [
{
"id": "ozz_move_01HX9R3KQMDF5NZTY2WB8CLPV",
"title": "Cut dining out by $150",
"description": "You spent $480 on restaurants this month β $150 above your plan target. Cook 3 more meals at home per week.",
"potential_savings_cents": 15000,
"category": "food",
"priority": "high"
},
{
"id": "ozz_move_01HX9R3KQMDF5NZTY2WB8CLPW",
"title": "Audit subscriptions",
"description": "We detected 4 recurring charges. Cancelling unused ones could free up $45/month.",
"potential_savings_cents": 4500,
"category": "entertainment",
"priority": "medium"
}
]
}
}
Step 7 β Chatβ
Endpoint: POST /v1/users/{user_id}/chat
Context-aware: Ozzie's response uses the user's plan, goals, and transaction history.
Sample Request:
POST https://api.ozzieapp.com/v1/users/ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE/chat
Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=
Content-Type: application/json
{
"message": "Am I on track to hit my emergency fund goal?",
"language": "en"
}
Sample Response:
{
"object": "chat_message",
"data": {
"id": "ozz_chat_01HX9S4TRQNG6BWZM3KD7VCFP",
"user_id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"role": "assistant",
"message": "You're off to a great start! You've saved $0 toward your $5,000 Emergency Fund goal, which you want to reach by December 31. To stay on track, you need to save $625/month. Based on your current $1,800 surplus and your plan's $900 savings target, you're ahead β you have room to hit that milestone easily. Keep submitting your transactions so I can give you more precise updates!",
"created_at": "2025-05-05T14:35:00Z"
}
}
Onboarding Flowβ
The minimum sequence to fully onboard a new user is Steps 1β3. After these three calls, the user has a personalized financial plan and Ozzie can provide intelligent recommendations.
POST /v1/users β get user_id
POST /v1/users/{user_id}/financial-intake β store income/expenses/goal
POST /v1/users/{user_id}/plan/generate β create personalized plan
| Stage | onboarding_stage value | What's available |
|---|---|---|
| After Step 1 | intake_pending | User created, intake required |
| After Step 2 | plan_pending | Intake stored, plan required |
| After Step 3 | active | Full platform access |
If you only want to get users into Ozzie as fast as possible, collect monthly_income, monthly_expenses, and financial_goal alongside user creation and fire all three calls in rapid succession.
Ongoing Usageβ
After onboarding, the primary calls for an active user are:
| Call | When to make it | Frequency |
|---|---|---|
POST /v1/users/{user_id}/transactions | User submits an expense or income | Per transaction event |
GET /v1/users/{user_id}/transactions | Display transaction history | On demand |
GET /v1/users/{user_id}/money-moves | Show spending recommendations | Daily or on demand |
POST /v1/users/{user_id}/chat | User sends a message to Ozzie | Per chat message |
POST /v1/users/{user_id}/financial-intake | User's finances change significantly | Monthly or on change |
POST /v1/users/{user_id}/plan/generate | After updating the intake | After each new intake |
Call POST /v1/users/{user_id}/plan/generate whenever you submit a new financial intake. The new plan supersedes the old one and is used by all subsequent money-move and chat responses.
WhatsApp Flowβ
Ozzie supports webhook-based ingestion from WhatsApp. When a user sends a message (text, image, or document) via WhatsApp, the flow is:
User sends WhatsApp message
β
βΌ
Ozzie WhatsApp Webhook receives the message
β
βΌ
Ozzie resolves user by phone number (E.164)
β ββ if no match β prompts user to register
βΌ
POST /v1/users/{user_id}/transactions (auto-called internally)
β
βΌ
Ozzie replies via WhatsApp with parsed transaction confirmation
β
βΌ
User can follow up: "What did I spend this week?"
β
βΌ
POST /v1/users/{user_id}/chat (auto-called internally)
β
βΌ
Ozzie replies via WhatsApp with context-aware answer
To enable WhatsApp for a user, ensure their phone field is set in the user object (Step 1 or via PATCH /v1/users/{user_id}). Ozzie matches incoming WhatsApp messages to users by phone number in E.164 format.
Phone numbers must be stored in E.164 format (e.g., +15551234567). WhatsApp messages from numbers not matching any user record will receive an onboarding prompt and will not be logged as transactions.
Error States & Recoveryβ
| Situation | Error Code | Recovery |
|---|---|---|
Call plan/generate before intake | INTAKE_REQUIRED | Call POST /v1/users/{user_id}/financial-intake first, then retry |
| Call money-moves before plan | PLAN_REQUIRED | Call POST /v1/users/{user_id}/plan/generate first, then retry |
| Call chat before personality is set | PERSONALITY_REQUIRED | Ozzie auto-sets personality after plan generation; retry after plan |
Create user with duplicate external_user_id | CONFLICT | Use GET /v1/users/{user_id} to retrieve the existing user |
| Invalid field value (e.g., bad email) | VALIDATION_ERROR | Fix the field per the error message and retry |
| Too many requests | RATE_LIMIT_EXCEEDED | Respect Retry-After header; implement exponential backoff |
Error response shape:
{
"error": {
"code": "INTAKE_REQUIRED",
"message": "A financial intake snapshot is required before generating a plan. Call POST /v1/users/{user_id}/financial-intake first."
}
}
Complete Node.js End-to-End Exampleβ
This example chains all 7 steps sequentially, handling errors at each stage.
import fetch from 'node-fetch';
const BASE_URL = 'https://api.ozzieapp.com/v1';
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const AUTH_TOKEN = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
const headers = {
'Authorization': `Bearer ${AUTH_TOKEN}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
};
async function ozzieRequest(method, path, body) {
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) {
throw new Error(`[${json.error?.code}] ${json.error?.message}`);
}
return json.data;
}
async function main() {
// ββ Step 1: Create User ββββββββββββββββββββββββββββββββββββββββββ
console.log('Step 1: Creating user...');
const user = await ozzieRequest('POST', '/users', {
external_user_id: 'usr_8821',
name: 'Maria Santos',
email: 'maria@example.com',
phone: '+15551234567',
language: 'en',
});
console.log(` β User created: ${user.id}`);
const userId = user.id;
// ββ Step 2: Financial Intake βββββββββββββββββββββββββββββββββββββ
console.log('Step 2: Sending financial intake...');
const intake = await ozzieRequest('POST', `/users/${userId}/financial-intake`, {
monthly_income: 5000,
monthly_expenses: 3200,
financial_goal: 'savings',
next_pay_date: '2025-05-15',
});
console.log(` β Intake recorded. Monthly surplus: $${intake.monthly_surplus}`);
// ββ Step 3: Generate Plan ββββββββββββββββββββββββββββββββββββββββ
console.log('Step 3: Generating plan...');
const plan = await ozzieRequest('POST', `/users/${userId}/plan/generate`, {});
console.log(` β Plan created. Recommended savings: $${plan.recommended_monthly_savings}/month`);
// ββ Step 4: Set Goal βββββββββββββββββββββββββββββββββββββββββββββ
console.log('Step 4: Setting goal...');
const goal = await ozzieRequest('POST', `/users/${userId}/goals`, {
name: 'Emergency Fund',
target_amount: 5000,
currency: 'USD',
target_date: '2025-12-31',
});
console.log(` β Goal set: "${goal.name}" β need $${goal.monthly_contribution_needed}/month`);
// ββ Step 5: Submit Transactions ββββββββββββββββββββββββββββββββββ
console.log('Step 5: Submitting transactions...');
const txResult = await ozzieRequest('POST', `/users/${userId}/transactions`, {
type: 'text',
content: 'Spent $45 on groceries at Whole Foods and $12 on the subway',
language: 'en',
});
console.log(` β Parsed ${txResult.transactions.length} transactions`);
// ββ Step 6: Get Money Moves ββββββββββββββββββββββββββββββββββββββ
console.log('Step 6: Fetching money moves...');
const movesResult = await ozzieRequest('GET', `/users/${userId}/money-moves`);
console.log(` β Received ${movesResult.moves.length} money move recommendations`);
movesResult.moves.forEach(m => console.log(` β’ [${m.priority}] ${m.title}`));
// ββ Step 7: Chat βββββββββββββββββββββββββββββββββββββββββββββββββ
console.log('Step 7: Chatting with Ozzie...');
const chatReply = await ozzieRequest('POST', `/users/${userId}/chat`, {
message: 'Am I on track to hit my emergency fund goal?',
language: 'en',
});
console.log(` β Ozzie says: "${chatReply.message}"`);
console.log('\nAll steps complete! User is fully onboarded and active.');
}
main().catch(err => {
console.error('Integration failed:', err.message);
process.exit(1);
});
Install the dependency with npm install node-fetch, then run with node integration.js. Replace your_client_id and your_client_secret with your actual credentials from the Ozzie developer dashboard.