Skip to main content

API Overview

Base URL & Versioning​

The Ozzie API is versioned via the URL path. The current stable version is v1.

https://api.ozzieapp.com/v1

Versioning policy:

  • The version prefix (/v1, /v2, etc.) is incremented only for breaking changes.
  • Additive changes (new optional fields, new endpoints) are made without a version bump.
  • Deprecated versions are announced at least 90 days before sunset and will return a Deprecation response header during the sunset window.
  • Always pin to an explicit version in your base URL. Never use /latest.
Version header

When a version enters its deprecation window, all responses will include:

Deprecation: true
Sunset: Sat, 01 Nov 2025 00:00:00 GMT

All Available Endpoints​

MethodPathDescription
POST/v1/usersCreate a new user
GET/v1/users/{user_id}Retrieve a user by Ozzie ID or external_user_id
PATCH/v1/users/{user_id}Update user fields (name, email, phone, language)
POST/v1/users/{user_id}/financial-intakeSubmit a monthly financial snapshot
GET/v1/users/{user_id}/financial-intakeGet the most recent financial intake
GET/v1/users/{user_id}/financial-intake/historyGet all historical intake snapshots
POST/v1/users/{user_id}/plan/generateGenerate a personalized financial plan
GET/v1/users/{user_id}/planGet the current active plan
POST/v1/users/{user_id}/goalsCreate a financial goal
GET/v1/users/{user_id}/goalsList all goals
GET/v1/users/{user_id}/goals/{goal_id}Get a specific goal
PATCH/v1/users/{user_id}/goals/{goal_id}Update a goal
DELETE/v1/users/{user_id}/goals/{goal_id}Delete a goal
POST/v1/users/{user_id}/transactionsSubmit transactions (text, image, PDF, spreadsheet)
GET/v1/users/{user_id}/transactionsList transactions with filters
GET/v1/users/{user_id}/transactions/{transaction_id}Get a specific transaction
DELETE/v1/users/{user_id}/transactions/{transaction_id}Delete a transaction
GET/v1/users/{user_id}/money-movesGet personalized spending recommendations
POST/v1/users/{user_id}/chatSend a message to Ozzie's financial assistant
GET/v1/users/{user_id}/chat/historyGet chat message history

Authentication​

All API requests must include an Authorization header using HTTP Bearer authentication. The token is the Base64 encoding of your client_id and client_secret joined by a colon.

Token format:

base64(client_id + ":" + client_secret)

Header format:

Authorization: Bearer <base64_encoded_credentials>

Generating the token:

const token = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
// Use as: `Bearer ${token}`
Keep credentials secret

Never expose your client_secret in client-side code, mobile apps, or public repositories. All API calls must originate from your backend server.


Request & Response Conventions​

Request Headers​

HeaderRequiredValue
AuthorizationYesBearer <token>
Content-TypeYes (for POST/PATCH)application/json
AcceptNo (recommended)application/json
Idempotency-KeyNoUUID v4 string for idempotent POST requests

Response Envelope​

All successful responses follow this envelope structure:

{
"object": "object_type_name",
"data": { ... }
}

For list endpoints:

{
"object": "list",
"data": {
"items": [ ... ],
"has_more": true,
"next_cursor": "2025-04-30T12:00:00Z",
"total_count": 142
}
}

HTTP Status Codes​

StatusMeaning
200 OKRequest succeeded
201 CreatedResource was created
204 No ContentResource was deleted
400 Bad RequestMalformed JSON or validation error
401 UnauthorizedMissing or invalid credentials
403 ForbiddenValid credentials, but insufficient permissions
404 Not FoundResource does not exist
409 ConflictResource already exists (e.g., duplicate user)
422 Unprocessable EntityBusiness logic error (e.g., intake missing)
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorOzzie server error

Pagination​

List endpoints use cursor-based pagination with ISO datetime cursors. This approach is stable even when new records are inserted between pages.

Query Parameters​

ParameterTypeDefaultDescription
limitinteger50Number of records per page (max 200)
cursorstringβ€”ISO 8601 datetime string β€” returns records older than this timestamp
fromstringβ€”Filter: include records on or after this date (YYYY-MM-DD)
tostringβ€”Filter: include records on or before this date (YYYY-MM-DD)

Paginated Response Fields​

FieldTypeDescription
has_morebooleantrue if additional pages exist
next_cursorstring | nullPass this as cursor on the next request to get the next page
total_countintegerTotal number of matching records (not just this page)

Pagination Example​

async function fetchAllTransactions(userId) {
const allTransactions = [];
let cursor = null;

do {
const params = new URLSearchParams({ limit: '200' });
if (cursor) params.set('cursor', cursor);

const res = await fetch(
`https://api.ozzieapp.com/v1/users/${userId}/transactions?${params}`,
{ headers }
);
const json = await res.json();
allTransactions.push(...json.data.transactions);

cursor = json.data.has_more ? json.data.next_cursor : null;
} while (cursor);

return allTransactions;
}
Cursor stability

Cursors are based on created_at timestamps. Records created after you start paginating will not appear in the current page sequence β€” start a fresh request to pick up new records.


Error Codes​

All errors follow this shape:

{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable explanation of what went wrong."
}
}
CodeHTTP StatusDescription
INVALID_JSON400The request body is not valid JSON. Check for trailing commas, missing quotes, or encoding issues.
VALIDATION_ERROR400One or more fields failed validation. The message will identify the specific field and constraint.
UNAUTHORIZED401The Authorization header is missing, malformed, or the credentials are invalid.
FORBIDDEN403The credentials are valid but do not have permission to access this resource (e.g., accessing another client's user).
NOT_FOUND404The requested resource (user, transaction, goal, etc.) does not exist.
CONFLICT409A resource with a conflicting unique key already exists (e.g., external_user_id already in use).
INTAKE_REQUIRED422A financial intake snapshot must be submitted before this operation (e.g., plan generation).
PLAN_REQUIRED422A financial plan must be generated before this operation (e.g., money moves).
PERSONALITY_REQUIRED422Ozzie's personality profile for this user has not been initialized yet. This resolves automatically after plan generation.
RATE_LIMIT_EXCEEDED429You have exceeded the request rate limit. See the Retry-After header for when to retry.
VERSION_MISMATCH400The API version you are targeting is no longer supported. Update your base URL to the current version.
INTERNAL_ERROR500An unexpected error occurred on Ozzie's servers. These are automatically logged and investigated. Retry with exponential backoff.

Rate Limiting​

Rate limits are applied per client_id. Current limits:

TierRequests per minuteBurst
Standard12020
Growth600100
EnterpriseCustomCustom

When the limit is exceeded, the API returns HTTP 429 with:

HTTP/1.1 429 Too Many Requests
Retry-After: 15
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1746460800
Response HeaderDescription
Retry-AfterSeconds to wait before retrying
X-RateLimit-LimitYour total requests-per-minute allowance
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets
Implement backoff

Always implement exponential backoff when handling 429 responses. A simple retry loop without delay will worsen rate limit pressure. Start with a 1-second delay, doubling up to 32 seconds.


Idempotency​

POST requests that create resources support idempotency via the Idempotency-Key header. If you supply the same key on a repeated request (e.g., after a network timeout), Ozzie will return the original response instead of creating a duplicate.

POST https://api.ozzieapp.com/v1/users
Authorization: Bearer czJjbGllbnQ6czJzZWNyZXQ=
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

{
"external_user_id": "usr_8821",
"name": "Maria Santos"
}

Rules:

  • The key must be a UUID v4.
  • Keys are scoped to your client_id and expire after 24 hours.
  • If the original request is still in flight, the retry will receive HTTP 409 with code CONFLICT until the original completes.
  • Supported on: POST /v1/users, POST /v1/users/{user_id}/financial-intake, POST /v1/users/{user_id}/transactions.
Not all POSTs are idempotent

POST /v1/users/{user_id}/plan/generate and POST /v1/users/{user_id}/chat are not idempotent by design β€” each call intentionally generates a new resource.