Skip to main content

External ID Integration

The Ozzie API supports two distinct integration patterns. You can choose whichever fits your architecture β€” or mix both within the same client account.


Two Integration Patterns​

Flow A β€” Explicit user creation​

You create a user record first by calling POST /v1/users, receive an Ozzie UUID (ozz_usr_...), and use that UUID in all subsequent requests.

POST /v1/users β†’ returns id: "ozz_usr_abc123"
POST /v1/users/ozz_usr_abc123/financial-intake β†’ uses Ozzie UUID
POST /v1/users/ozz_usr_abc123/plan/generate β†’ uses Ozzie UUID
POST /v1/users/ozz_usr_abc123/chat/messages β†’ uses Ozzie UUID

Best for: integrations where you want full control over user creation, or where you need to store the Ozzie UUID in your own database.


Flow B β€” External ID auto-provisioning​

You skip user creation entirely. Use the external:{your_id} format as the {user_id} path parameter in any endpoint. On the first call, Ozzie automatically creates the user behind the scenes and continues processing the request.

POST /v1/users/external:usr_8821/financial-intake β†’ user auto-created on first call
POST /v1/users/external:usr_8821/plan/generate β†’ same user, no creation step needed
POST /v1/users/external:usr_8821/chat/messages β†’ same user

Best for: integrations where you don't want to manage a separate user creation step, or where you're connecting an existing user base without migrating IDs.

info

There is no configuration or flag needed to use Flow B. Any non-UUID value prefixed with external: is automatically treated as an external ID lookup. If the user does not exist yet, they are created silently.


How external:{id} works​

When Ozzie receives a request with external:{your_id} as the user_id path parameter, it:

  1. Strips the external: prefix and extracts your ID
  2. Looks up the user by (api_client_id, external_user_id)
  3. If found β†’ proceeds with the request using the matched user
  4. If not found β†’ auto-creates a new user with external_user_id = your_id and created_via = "api_external", then proceeds with the request

The auto-created user starts with onboarding_stage: "financial_intake". Ozzie advances the stage automatically after intake and plan generation, just as with users created via POST /v1/users.


Choosing between the two flows​

SituationRecommended pattern
You want to register users with name, email, and phone before they interactFlow A β€” POST /v1/users
You have an existing user database and want zero migration frictionFlow B β€” external:{id} on any endpoint
You're building a WhatsApp bot or conversational interfaceFlow A β€” set phone on creation for number matching
You're a fintech app proxying requests for your own usersFlow B β€” your database ID as the external ID
You need both options in the same integrationMix freely β€” each user can be created either way

Flow B β€” Full example​

This example sends financial intake, generates a plan, and starts a chat β€” all without ever calling POST /v1/users:

# Step 1 β€” Send financial intake (user is auto-created on this call)
curl -X POST https://api.ozzieapp.com/v1/users/external:usr_8821/financial-intake \
-H "Authorization: Bearer YOUR_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"monthly_income": 5000,
"monthly_expenses": 3200,
"financial_goal": "savings"
}'

# Step 2 β€” Generate plan
curl -X POST https://api.ozzieapp.com/v1/users/external:usr_8821/plan/generate \
-H "Authorization: Bearer YOUR_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'

# Step 3 β€” Chat
curl -X POST https://api.ozzieapp.com/v1/users/external:usr_8821/chat/messages \
-H "Authorization: Bearer YOUR_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "How much should I save this month?"}'

Retrieving an auto-provisioned user​

Users created via Flow B can be retrieved at any time using GET /v1/users/external:{id} or by their Ozzie UUID once you know it:

curl https://api.ozzieapp.com/v1/users/external:usr_8821 \
-H "Authorization: Bearer YOUR_BEARER_TOKEN"
{
"object": "user",
"data": {
"id": "ozz_usr_01HX9KZMR4P5JQNBVT7YCW3DE",
"external_user_id": "usr_8821",
"name": null,
"email": null,
"phone": null,
"language": "en",
"onboarding_stage": "active",
"created_at": "2026-05-06T10:00:00Z"
}
}

Auto-provisioned users start with name, email, and phone as null. You can enrich the record later β€” contact commercial@ozzieapp.com if you need a user update endpoint.


Mixing both flows​

Both patterns are fully compatible within the same API client account. Some users can be created explicitly via POST /v1/users (e.g., users who sign up with name and email), while others are auto-provisioned via external:{id} (e.g., anonymous users or users imported from an existing system).

The only constraint is that external_user_id must be unique per API client. An explicit POST /v1/users with the same external_user_id as an auto-provisioned user will return a 409 CONFLICT.


Security considerations​

  • The external: prefix is resolved after authentication. Unauthenticated requests cannot use it.
  • Users auto-provisioned via Flow B are scoped to your API client. Another client cannot access them even if they know the external_user_id.
  • Rate limits apply equally to both flows. Auto-provisioning a new user does not consume an extra API call β€” it is part of the same request.