Skip to main content

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.

Two integration patterns

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.

FieldTypeRequiredDescription
external_user_idstringYesYour internal user ID (max 100 chars)
namestringNoDisplay name
emailstringNoValid email address
phonestringNoE.164 format, e.g. +15551234567
languagestringNo"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"
}
}
info

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.

FieldTypeRequiredDescription
monthly_incomenumberYesGross monthly income in the user's base currency
monthly_expensesnumberYesTotal monthly outgoings
financial_goalstringYes"savings" | "debt"
next_pay_datestringNoISO 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
Stageonboarding_stage valueWhat's available
After Step 1intake_pendingUser created, intake required
After Step 2plan_pendingIntake stored, plan required
After Step 3activeFull platform access
Minimum viable onboarding

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:

CallWhen to make itFrequency
POST /v1/users/{user_id}/transactionsUser submits an expense or incomePer transaction event
GET /v1/users/{user_id}/transactionsDisplay transaction historyOn demand
GET /v1/users/{user_id}/money-movesShow spending recommendationsDaily or on demand
POST /v1/users/{user_id}/chatUser sends a message to OzziePer chat message
POST /v1/users/{user_id}/financial-intakeUser's finances change significantlyMonthly or on change
POST /v1/users/{user_id}/plan/generateAfter updating the intakeAfter each new intake
Re-generating the plan

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.

WhatsApp phone matching

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​

SituationError CodeRecovery
Call plan/generate before intakeINTAKE_REQUIREDCall POST /v1/users/{user_id}/financial-intake first, then retry
Call money-moves before planPLAN_REQUIREDCall POST /v1/users/{user_id}/plan/generate first, then retry
Call chat before personality is setPERSONALITY_REQUIREDOzzie auto-sets personality after plan generation; retry after plan
Create user with duplicate external_user_idCONFLICTUse GET /v1/users/{user_id} to retrieve the existing user
Invalid field value (e.g., bad email)VALIDATION_ERRORFix the field per the error message and retry
Too many requestsRATE_LIMIT_EXCEEDEDRespect 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);
});
Running this example

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.