Example: Building a Mobile App
Everything a consumer mobile or web app needs sits on top of the same REST + real-time surface. This is a worked example of stitching those APIs together into a client — auth, a chat turn, files, and notifications — in the order you'd typically build them.
Treat it as an illustration, not a spec: adapt the flow to whatever platform or framework you're on.
Authentication
Client apps send two credentials on every request:
| Header | Value | Identifies |
|---|---|---|
X-API-Key | your publishable key (pk_…) | your app / tenant |
Authorization | Bearer <Firebase ID token> | the signed-in end user |
The publishable key is safe to ship in the app binary — it can only act on behalf of an authenticated user and cannot be used for backend/admin operations (see Publishable Key Restrictions). The Firebase ID token is what actually carries user identity: the platform derives user_id, memory scope, and per-user data from it. You do not put a user ID in request bodies.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/list-threads \
-H "X-API-Key: pk_your_publishable_key" \
-H "Authorization: Bearer <firebase_id_token>" \
-H "Content-Type: application/json" \
-d '{}'
Signing in the user
Users sign in with Firebase Authentication (email/password, Sign in with Apple, etc.) using the Firebase config for your tenant. After sign-in, get an ID token from the Firebase SDK:
let token = try await Auth.auth().currentUser?.getIDToken()
The same token authenticates both REST calls and the real-time stream.
Refreshing the token
Firebase ID tokens expire after ~1 hour. Cache the token and refresh it before it expires rather than on every call:
- Parse
expfrom the token payload and treat it as expired ~5 minutes early to absorb clock skew and in-flight requests. - When within that buffer, force a refresh (
getIDToken(forcingRefresh: true)), and coalesce concurrent refreshes so a burst of requests triggers one refresh, not many.
Handling 401 and 429
| Status | What it means | What to do |
|---|---|---|
| 401 | Token expired or invalid | Force-refresh the Firebase token once and retry the request. If it still fails, treat the session as invalid and send the user back to sign-in. |
| 429 | Rate limited | Back off using the Retry-After / X-RateLimit-Reset response headers, then retry. See Rate Limiting. |
A Chat Turn
The core loop is: create a thread once, then send messages and stream replies.
- Create a thread —
create-threadreturns athreadId. Reuse it asconversation_keyfor the whole conversation. - Send a message —
send-messagereturns arunIdimmediately; generation runs in the background. - Show the reply in real time — subscribe to the streaming node for live tokens, or poll
conversation-stateif you don't use Firebase. - Track busy state — watch the conversation status node to enable/disable the composer.
See the Conversations guide for message shape, generation config, and tool calling.
Synchronous & Structured Generation
When you need the answer in the HTTP response itself — structured output (a JSON plan, a form, a classification) rather than a streamed chat bubble — use send-message-sync. It blocks until generation finishes and returns the messages inline. Send an Idempotency-Key so a retried request doesn't run generation twice.
Files
Attach a file with a single call and reference it by ID afterward:
POST /api/v1/storage/gateway/upload-filewith the base64content→ returns afileId. See Inline Upload. Use pre-signed upload for large files.- Keep only the
fileId. Reference it in message content — the platform resolves a fresh signed download URL just-in-time, so you never store or expire URLs client-side.
Notifications
- Register for push on login:
POST /api/v1/notifications/register-push-devicewith the device's FCM token andplatform: "PLATFORM_IOS"(or Android). Unregister on logout. See Push Device Registration. - Real-time in-app inbox: call
get-inbox-sessionfor a WebSocket URL + token, connect for live badge/feed updates, and fall back to polling the unseen count if the socket drops.
Checklist
A minimal client covers:
- Firebase Auth sign-in → ID token, with 5-minute-early refresh
- Publishable key +
Bearertoken on every request; 401→refresh→retry, 429→back off - Create thread → send message → stream (or poll) the reply
- File upload by
fileId - Push registration + real-time inbox
Related
- Authentication — All credential types in one place
- Streaming — Real-time token delivery over Firebase
- Conversations — Threads, messages, sync generation, tools
- Storage — File upload and download
- Notifications — Push and in-app inbox