Fintech App Integration
This guide walks through embedding Ozzie into an existing fintech or banking application. By the end, your app will onboard users into Ozzie, parse their transactions, display their financial plan, and surface an in-app AI coach β all backed by ozzie-openapi.
Architecture overviewβ
Your App (Frontend)
|
| API calls
v
Your Backend Server
|
|ββ POST /v1/users (user creation)
|ββ POST /v1/users/{id}/financial-intake (financial intake)
|ββ POST /v1/personality/score (personality scoring)
|ββ POST /v1/users/{id}/plan/generate (plan generation)
|ββ POST /v1/users/{id}/chat/completion (AI coaching)
|ββ POST /v1/transactions/parse (transaction parsing)
|ββ POST /v1/money-moves/generate (weekly move generation)
v
ozzie-openapi (api.ozzieapp.com/v1)
Key principle: your frontend never calls Ozzie directly. All Ozzie API calls are made server-side, where your credentials are secure. Your frontend talks to your backend, which orchestrates Ozzie calls and stores all results.
Prerequisitesβ
- An Ozzie API
client_idandclient_secret(get them at the Ozzie dashboard or email commercial@ozzieapp.com) - A backend server (Node.js examples below, but the pattern applies to any stack)
- A database to store user records, personality types, conversation history, and transaction records
Step 1: Create an Ozzie user on signupβ
When a new user completes registration in your app, create their Ozzie profile.
// lib/ozzie.js β shared Ozzie client
const BASE_URL = 'https://api.ozzieapp.com/v1';
const token = Buffer.from(
`${process.env.OZZIE_CLIENT_ID}:${process.env.OZZIE_CLIENT_SECRET}`
).toString('base64');
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
};
export async function ozzieRequest(method, path, body = null) {
const response = await fetch(`${BASE_URL}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Ozzie API error: ${error.error?.code} β ${error.error?.message}`);
}
return response.json();
}
// routes/auth.js β called during user registration
import { ozzieRequest } from '../lib/ozzie.js';
import { db } from '../lib/db.js';
export async function handleSignup(req, res) {
const { email, name, language } = req.body;
// 1. Create your internal user
const internalUser = await db.users.create({ email, name, language });
// 2. Create the corresponding Ozzie user
const { data: ozzieUser } = await ozzieRequest('POST', '/users', {
external_user_id: internalUser.id,
name: internalUser.name,
language: internalUser.language || 'en',
});
// 3. Store the Ozzie user ID for future API calls
await db.users.update(internalUser.id, { ozzie_user_id: ozzieUser.id });
res.json({ user: internalUser });
}
Store ozzie_user_id in your users table alongside your own user ID. Every subsequent Ozzie call requires it.
Step 2: Collect financial intakeβ
After signup, prompt the user to complete their financial profile.
// routes/onboarding.js
export async function submitFinancialIntake(req, res) {
const { userId } = req.user;
const user = await db.users.findById(userId);
const { data: intake } = await ozzieRequest(
'POST',
`/users/${user.ozzie_user_id}/financial-intake`,
{
monthly_income: req.body.monthlyIncome,
monthly_expenses: req.body.monthlyExpenses,
financial_goal: req.body.financialGoal,
}
);
res.json({ intake });
}
Step 3: Score personality and generate the planβ
// Full onboarding function β intake + personality + plan + first money move
export async function completeOnboarding(userId, answers) {
const user = await db.users.findById(userId);
const ozId = user.ozzie_user_id;
// Score personality from quiz answers
const { data: score } = await ozzieRequest('POST', '/personality/score', { answers });
const personalityType = score.personality_type;
// Store personality type in your database
await db.users.update(userId, { personality_type: personalityType });
// Generate the plan
const { data: plan } = await ozzieRequest('POST', `/users/${ozId}/plan/generate`, {
personality_type: personalityType,
});
// Store plan_tier for money move generation
await db.users.update(userId, { plan_tier: plan.plan_tier });
// Generate the first money move cycle
const { data: moves } = await ozzieRequest('POST', '/money-moves/generate', {
user_id: ozId,
personality_type: personalityType,
plan_tier: plan.plan_tier,
});
// Store move tasks in your database
await db.moneyMoves.create({ user_id: userId, ...moves });
return { plan, personalityType, moves };
}
Step 4: Display Ozzie data in your UIβ
Your frontend fetches data from your backend, which serves stored data and calls Ozzie when needed.
// Backend route β serves plan data to your frontend
app.get('/api/dashboard', requireAuth, async (req, res) => {
const user = await db.users.findById(req.user.id);
const ozId = user.ozzie_user_id;
const [planRes, currentMove] = await Promise.all([
ozzieRequest('GET', `/users/${ozId}/plan`),
db.moneyMoves.findCurrentWeek(req.user.id),
]);
res.json({
plan: planRes.data,
personalityType: user.personality_type,
currentMove,
});
});
Frontend (React example):
function Dashboard() {
const { data } = useSWR('/api/dashboard', fetcher);
if (!data) return <Spinner />;
const { plan, currentMove } = data;
return (
<div>
<h2>Your Financial Plan</h2>
<BudgetPieChart allocations={plan.envelopes} />
{currentMove && (
<MoneyMoveCard
tasks={currentMove.tasks}
weekEnd={currentMove.week_end}
/>
)}
<ActionItems items={plan.action_items} />
</div>
);
}
Step 5: Embed the AI coachβ
// Backend route
app.post('/api/chat', requireAuth, async (req, res) => {
const user = await db.users.findById(req.user.id);
const { message } = req.body;
if (!message || message.trim().length === 0) {
return res.status(400).json({ error: 'Message is required' });
}
// Load conversation history from your database
const history = await db.messages.findRecent(req.user.id, 10);
const { data: reply } = await ozzieRequest(
'POST',
`/users/${user.ozzie_user_id}/chat/completion`,
{
message,
personality_type: user.personality_type,
history,
}
);
// Store both the user message and the reply
await db.messages.create({ user_id: req.user.id, role: 'user', content: message });
await db.messages.create({ user_id: req.user.id, role: 'assistant', content: reply.content });
res.json(reply);
});
Step 6: Parse transactionsβ
// Backend route β parse a transaction submitted from your app
app.post('/api/transactions', requireAuth, async (req, res) => {
const user = await db.users.findById(req.user.id);
const { type, content, mimeType } = req.body;
const { data } = await ozzieRequest('POST', '/transactions/parse', {
type,
content,
mime_type: mimeType,
});
// Store parsed transactions in your database
const stored = await Promise.all(
data.transactions.map(tx =>
db.transactions.create({ user_id: req.user.id, ...tx })
)
);
res.json({ transactions: stored });
});
Tips and best practicesβ
Language detectionβ
Use the user's browser locale or device language to set their Ozzie language preference on creation:
const language = req.headers['accept-language']?.split(',')[0]?.split('-')[0] ?? 'en';
const ozzieLanguage = ['en', 'pt', 'es'].includes(language) ? language : 'en';
Error handlingβ
Wrap all Ozzie calls in try/catch and handle known error codes gracefully:
try {
const { data } = await ozzieRequest('POST', `/users/${ozId}/plan/generate`, {
personality_type: user.personality_type,
});
return data;
} catch (err) {
if (err.message.includes('INTAKE_REQUIRED')) {
return { redirect: '/onboarding/intake' };
}
if (err.message.includes('RATE_LIMIT_EXCEEDED')) {
await delay(5000);
return ozzieRequest('POST', `/users/${ozId}/plan/generate`, {
personality_type: user.personality_type,
});
}
throw err;
}
Caching plan dataβ
Cache Ozzie plan responses in your database so users see data even when the API is temporarily unavailable:
async function getPlanWithCache(userId) {
const user = await db.users.findById(userId);
try {
const { data: plan } = await ozzieRequest('GET', `/users/${user.ozzie_user_id}/plan`);
await db.plans.upsert({ user_id: userId, data: plan, cached_at: new Date() });
return plan;
} catch {
const cached = await db.plans.findOne({ user_id: userId });
if (cached) return cached.data;
throw new Error('Plan unavailable and no cache exists');
}
}