Worked example · for developers
Connecting one app, end to end.
The integration guide is the reference — every piece, in isolation. This is the tutorial: one concrete app built start to finish, so you can see how the pieces fit, and the failure modes you'll meet on the way. The app here is a simple bookings platform — customers have a profile, book appointments with providers, and reschedule or cancel them.
01 — The scenario
The scenario
Everything on our bookings platform lives behind a login: your profile, your upcoming appointments, your account balance. Before this integration, the support assistant could explain how booking works — but a customer asking "do I have anything booked this week?" got a generic answer, because the assistant had no idea who was typing.
The goal is to close exactly that gap: let the assistant read the customer's own account and act on it — book, reschedule, cancel — in the same chat, in Arabic or English, without ever exposing a real credential to the language model.
If a term is new — handoff token, Bridge, the identity allowlist — the integration guide defines it. This page shows it in one concrete app, with real consequences.
02 — The result
What the assistant can do
Seven operations. Each answers a real thing customers ask — and they ask in Arabic, Iraqi dialect, and English, often mid-sentence.
list_appointments → “You have one on Thursday at 5 PM with Dr. Sara.”cancel_appointment → “Done — your Thursday 5 PM appointment is cancelled.”list_providers then book_appointment → “What time on July 23rd, and with which provider?”The full allowlist
// A bookings platform's allowlist — the exact surface the assistant is given. // Reads answer "what's my…"; writes act on the customer's own account. allow = { // ── reads ── get_profile: { GET, "/api/users/me" }, list_appointments: { GET, "/api/appointments/me" }, list_providers: { GET, "/api/providers" }, get_wallet: { GET, "/api/users/me/wallet" }, // ── writes — your own API still enforces every rule (availability, lead time, limits) ── book_appointment: { POST, "/api/appointments" }, reschedule_appointment: { PATCH, "/api/appointments/{appointmentId}/reschedule" }, cancel_appointment: { POST, "/api/appointments/{appointmentId}/cancel" }, }
Ship the four reads and verify them before switching the writes on. A read that misbehaves is an information issue; a write that misfires books or cancels a real appointment. Your own API keeps enforcing its rules — availability, lead times, limits — so the Bridge grants reach, never permission.
03 — The build
Building it in order
Four pieces, three of them yours. Budget about a day.
- A mint endpoint. An authenticated route that returns a short-lived opaque token for the current user. (Where it lives matters more than it looks — see the failure modes.)
- An exchange endpoint. Server-to-server, shared-secret, turning that token into a real user token — and refusing to do so for admin accounts, so support can never become an escalation path.
- A small Bridge service. The seven-operation allowlist above, and nothing else. Path ids like
{appointmentId}are filled from the request body; the user's real token never leaves the Bridge. - One widget attribute + seven dashboard actions. The embed gains a
data-end-user-token-url; each bridge operation becomes an action whose only auth header is{{ctx:end_user_token}}.
The integration guide has the full code for each of these — this walkthrough is about the order and the pitfalls, not re-printing it.
Do all of this against an isolated staging copy — its own database, its own secrets, customer data scrubbed of real emails and phone numbers. The failure modes below are much cheaper to meet there than in production.
04 — What goes wrong
Failure modes to expect
An integration can "work" on day one and still feel broken. These are the traps that bite in practice — every one is invisible until a real conversation surfaces it.
1 · The bot "forgets who you are" after an hour
The headline symptom: an action fails with the assistant insisting it doesn't know who the customer is — even though they're plainly signed in. It's reproducible, but only an hour or so into a session.
The cause is almost always the mint endpoint being authenticated by a short-lived access token. Once that lapses, the mint returns 401, the widget sends no identity, and every operation fails as anonymous while the person is still logged in.
# The failure, reproduced deterministically: # drop the short-lived access cookie, keep the long-lived refresh cookie, mint again. grep -v access_token jar.txt > expired.txt curl -b expired.txt -X POST $API/api/support/handoff-token # → 401 ✗ (mint under /api/support) curl -b expired.txt -X POST $API/api/auth/support-handoff-token # → 200 ✓ (mint under /api/auth) # If your refresh cookie is path-scoped to /api/auth, it never reaches /api/support. # Put the mint endpoint where the long-lived credential already travels.
Put the mint endpoint where your long-lived (refresh) credential already reaches, and validate that credential read-only — never rotate it, or you could log the user out from a support chat. This is the "credential-lifetime trap", and the reason the guide insists you test the expiry case before shipping.
2 · A wrong answer that won't go away
After identity is fixed, one exact question keeps replaying an old failure — while rewording it works perfectly. The assistant caches answers by question, not by user, for a few days. Anything asked during an outage has its failure answer frozen in, so a transient bug becomes a persistent one. Purge the tenant's answer cache; identity-bearing and action-driven answers aren't cached going forward.
3 · A blank reply
Occasionally an action succeeds and the model returns nothing — an empty bubble. A naive retry can make it worse (an identical request replays the identical empty result). The platform handles this for you now — it retries on a stronger model and only hands off to a human if that's empty too — but it's worth knowing the shape of it.
4 · It suddenly answers English in Arabic (or vice-versa)
With a bilingual knowledge base, a model can start following the language of the retrieved snippets or its own earlier turns rather than the customer's latest message. The platform now pins each reply to the language of the message it's answering — but if you see drift, it's this.
5 · The right data, the wrong number
If your API returns two fields that disagree — say a raw column and the value your system actually enforces — the model has no way to know which to trust, and will sometimes quote the wrong one. The fix is one sentence in the action's description: name the authoritative field and tell it to ignore the other.
6 · Trigger phrases that do nothing
The dashboard's "trigger phrases" field is not sent to the model. If you put your Arabic phrasings there expecting them to help the assistant choose the right operation, they won't. They belong in the description, which is the only free text the model actually reads.
05 — Takeaways
Lessons & tips, distilled
✓ Do
- Test the expiry case first — it's the break you'll actually hit.
- Put your phrasings in the description, not trigger phrases.
- Ship reads before writes; trust the logs before you trust the model with a booking.
- Name the authoritative field when your data has two that disagree.
- Build on staging with scrubbed data.
! Traps that look like other problems
- "The bot is dumb" is usually a stale cache or a dropped token — not the model.
- A ~1s reply is a cache hit, not speed. Latency tells you which.
- "It stopped doing the bad thing" ≠ the fix works — verify the mechanism, not just the symptom.
- An expired credential looks nothing like a crash; it looks like amnesia.
Almost every failure here is invisible — no error, no crash, just a subtly wrong or generic answer. The way to find them is to hold a real conversation, in your users' language, and watch the reply and the latency. A test suite proves the code runs; only a real chat proves the assistant is helpful.
06 — Summary
At a glance
| Dimension | This example |
|---|---|
| Operations exposed | 7 — four reads, three writes |
| New endpoints on your side | 2 (mint + exchange) plus one small Bridge service |
| Scode-side code | 0 — dashboard actions and one embed attribute |
| Credentials the model can see | 0 — only the opaque token travels, and even that is hidden from it |
| Languages handled in one chat | Arabic, Iraqi dialect, Franco-Arabic, English |
| Rough effort | About a day for a developer who knows your stack |
Ready to build? The integration guide has the code for every step, and its troubleshooting table is keyed on exactly the symptoms above.