Authentication
The Ozzie API uses HTTP Bearer authentication. Every request must include an Authorization header with a base64-encoded token derived from your client_id and client_secret.
Getting your credentials
To get API credentials, either:
- Contact the Ozzie team at commercial@ozzieapp.com to discuss your use case and get access
- Log in to the Ozzie dashboard and navigate to Settings → API Keys to generate a
client_idandclient_secretpair
You will receive two values:
| Value | Example | Description |
|---|---|---|
client_id | ozz_client_a1b2c3d4 | A stable identifier for your API client. Safe to log. |
client_secret | sk_live_xK9mP2qR7tL... | A secret key. Treat this like a password. Never expose it. |
Your client_secret is shown only once at generation time. Store it securely immediately — if you lose it, you will need to rotate your credentials.
How authentication works
Ozzie uses HTTP Basic Auth encoded as a Bearer token. The token is the base64 encoding of the string client_id:client_secret (colon-separated, no spaces).
token = base64("ozz_client_a1b2c3d4:sk_live_xK9mP2qR7tL...")
Authorization: Bearer <token>
This is a single, static token you generate once and reuse across all requests. You do not need to exchange it for a session token or refresh it on a schedule — it is valid until you rotate your credentials.
Encoding your token
- Shell
- Node.js
- Python
# Encode your credentials
CLIENT_ID="ozz_client_a1b2c3d4"
CLIENT_SECRET="sk_live_xK9mP2qR7tL..."
TOKEN=$(echo -n "$CLIENT_ID:$CLIENT_SECRET" | base64)
echo $TOKEN
# ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu...
const clientId = process.env.OZZIE_CLIENT_ID;
const clientSecret = process.env.OZZIE_CLIENT_SECRET;
const token = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
console.log(token);
// ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu...
import base64
import os
client_id = os.environ["OZZIE_CLIENT_ID"]
client_secret = os.environ["OZZIE_CLIENT_SECRET"]
token = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
print(token)
# ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu...
Making authenticated requests
Once you have the token, attach it to every request in the Authorization header:
- curl
- Node.js
- Python
curl https://api.ozzieapp.com/v1/api/v1/users \
-H "Authorization: Bearer ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu..." \
-H "Content-Type: application/json" \
-d '{
"external_user_id": "user_123",
"name": "Alex Johnson",
"language": "en"
}'
const token = Buffer.from(
`${process.env.OZZIE_CLIENT_ID}:${process.env.OZZIE_CLIENT_SECRET}`
).toString('base64');
const response = await fetch('https://api.ozzieapp.com/v1/api/v1/users', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
external_user_id: 'user_123',
name: 'Alex Johnson',
language: 'en',
}),
});
const data = await response.json();
console.log(data);
import base64
import os
import httpx
token = base64.b64encode(
f"{os.environ['OZZIE_CLIENT_ID']}:{os.environ['OZZIE_CLIENT_SECRET']}".encode()
).decode()
response = httpx.post(
"https://api.ozzieapp.com/v1/api/v1/users",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"external_user_id": "user_123",
"name": "Alex Johnson",
"language": "en",
},
)
print(response.json())
Build the token once at application startup and reuse it across all requests. There is no expiry — you don't need to regenerate it per-request.
Rate limiting
Every API response includes rate limit headers so you can track your usage:
| Header | Description |
|---|---|
X-RateLimit-Limit | The maximum number of requests allowed in the current window |
X-RateLimit-Remaining | How many requests you have left in the current window |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets and your limit replenishes |
Example response headers:
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1746489600
When you hit the rate limit, the API returns a 429 status. Back off and retry after the timestamp in X-RateLimit-Reset.
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "You have exceeded the rate limit for this window. Please retry after the reset time.",
"details": [
{
"reset_at": 1746489600,
"limit": 1000,
"window": "1 minute"
}
]
}
}
Rate limits vary by tier. Free tier clients have lower limits than Pro or Enterprise clients. Contact commercial@ozzieapp.com if you need higher throughput.
Authentication error codes
| HTTP Status | Error Code | When it happens |
|---|---|---|
401 Unauthorized | UNAUTHORIZED | The Authorization header is missing, malformed, or contains invalid credentials |
429 Too Many Requests | RATE_LIMIT_EXCEEDED | Your client has sent more requests than allowed in the current window |
Example 401 response:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing authorization credentials.",
"details": []
}
}
Common causes of a 401:
- Forgot to include the
Authorizationheader - Encoded
client_id:client_secretwith spaces or extra characters - Using a
client_secretthat was rotated or revoked - Sending the raw
client_id:client_secretstring instead of the base64-encoded version
Multi-tenancy and client scoping
Your client_id is the root of your tenant namespace. Every user you create with POST /users belongs to your client, and your API credentials can only access users you created. You cannot read or modify users created by other API clients.
This means:
- You manage all your end users through a single set of credentials
- Users are identified by your own
external_user_id— you own the mapping between Ozzie's user records and your database - All activity (financial intake, transactions, plans, chat) is scoped to users under your
client_id
There is no per-user authentication. Your server-side backend authenticates with Ozzie using your client credentials, then acts on behalf of your users. Never send your API credentials to a frontend client.
Security best practices
Never expose credentials client-side. Your client_id and client_secret should only live in your server-side environment. A browser or mobile app that calls the Ozzie API directly would expose your credentials to anyone who inspects network traffic.
Use environment variables. Store credentials in environment variables, not hardcoded in source files. This prevents accidental commits to version control.
# .env (never commit this file)
OZZIE_CLIENT_ID=ozz_client_a1b2c3d4
OZZIE_CLIENT_SECRET=sk_live_xK9mP2qR7tL...
Rotate secrets periodically. Generate a new client_secret from the Ozzie dashboard on a regular schedule (e.g., every 90 days), or immediately if you suspect a secret has been compromised. Update your environment variables and deploy before revoking the old secret.
Use separate credentials per environment. Create distinct API clients for your development, staging, and production environments so that test traffic never touches real user data, and a compromised development secret cannot affect production.
Scope your server's outbound access. If your infrastructure supports it, restrict outbound HTTPS calls to api.ozzieapp.com from only the services that need it — not every server in your stack.