End Users
Every person who talks to your agent has an end-user record: identity claims copied from your identity provider, plus the handful of settings they control themselves — where they are, what language they read, and what model defaults they prefer.
The platform reads this record on every turn. Setting a user's location means the agent knows their timezone without being told; setting their locale means it answers in their language without a prompt instruction.
There is deliberately no user identifier in any request body. The user is derived
server-side from the gateway-verified x-user-id / x-tenant-id headers, so a caller
can only read and write their own record — there is no way to address someone else's.
That means these endpoints need an end-user identity: a Firebase ID token, or a secret
key with X-On-Behalf-Of. A bare sk_… key returns
401 authenticated user_id is required.
Read the Profile
curl -X POST https://api.travila.ai/api/v1/enduser/get \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{}'
Response:
{
"endUser": {
"subject": "user_123",
"tenantId": "tenant_abc",
"email": "jane@example.com",
"emailVerified": true,
"name": "Jane Doe",
"givenName": "Jane",
"picture": "https://…",
"signInProvider": "google.com",
"locale": "en-US",
"zoneinfo": "Europe/London",
"location": {
"latitude": 51.5072,
"longitude": -0.1276,
"timezone": "Europe/London",
"updatedAt": "2026-08-14T09:31:02Z"
},
"localeOverride": "es-MX",
"defaultGenerationConfig": {
"model": "google/gemini-3.6-flash",
"temperature": 0.4,
"languagePreference": "es"
},
"metadata": {"plan": "pro"},
"firstSeenAt": "2026-01-14T09:12:00Z",
"lastSeenAt": "2026-08-14T09:30:58Z",
"updatedAt": "2026-08-14T09:31:02Z",
"createdAt": "2026-01-14T09:12:00Z"
}
}
The record mixes two kinds of field:
| Source | Fields | Who writes them |
|---|---|---|
| Identity provider | subject, email, emailVerified, name, givenName, familyName, preferredUsername, picture, phoneNumber, signInProvider, authMethod, locale, zoneinfo, customClaims | Copied from the verified token — read-only here |
| User settings | location, localeOverride, defaultGenerationConfig, metadata | The four update endpoints below |
If no row exists yet, the call still succeeds and returns an empty endUser object.
Treat "no profile yet" and "profile with no overrides" identically — do not branch on the
difference.
Remember that unset, empty, zero and false fields are omitted from the response
rather than sent as null. Read emailVerified with a default of false, not with a
presence check.
Update Location
curl -X POST https://api.travila.ai/api/v1/enduser/update-location \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"latitude": 51.5072,
"longitude": -0.1276
}'
Response:
{
"location": {
"latitude": 51.5072,
"longitude": -0.1276,
"timezone": "Europe/London",
"updatedAt": "2026-08-14T09:31:02Z"
}
}
Do not send a timezone. The IANA zone is derived server-side from the coordinates and returned on the response — that derivation is the main reason to call this endpoint at all, since it is what lets the agent reason about "this evening" correctly.
city is reserved for a later release and is currently always empty; there is no reverse
geocoding yet.
Update Locale
curl -X POST https://api.travila.ai/api/v1/enduser/update-locale \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{"locale": "es-MX"}'
Response: empty body on success.
A BCP-47 tag that overrides the locale supplied by the identity provider — for when the user picks a language in your app that differs from their account language.
Send an empty string to clear the override and fall back to the identity-provider locale:
{"locale": ""}
Update Default Generation Config
Sets the user's own generation defaults, applied as the base for their conversations. A conversation's own config still layers on top.
curl -X POST https://api.travila.ai/api/v1/enduser/update-generation-config \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"config": {
"model": "google/gemini-3.6-flash",
"temperature": 0.4,
"maxOutputTokens": 2048,
"languagePreference": "es"
}
}'
Response: empty body on success.
Only user-settable fields are accepted: model, models, the core sampling
parameters (temperature, topP, maxOutputTokens, frequencyPenalty,
presencePenalty, stopSequences, seed), and languagePreference.
Operational fields — systemPrompt, tools, clientTools, toolPolicy, MCP settings,
timeouts, anything else — are rejected, not silently dropped: a config carrying any
of them fails the whole request. The allowlist is closed by default, so fields added to
the generation config in future releases are rejected here until they are explicitly
opened up.
model and every entry in models are additionally checked against the platform model
allowlist; an unpermitted entry fails the request. This is the endpoint to expose behind
a user-facing "preferred model" setting — it cannot be used to smuggle in a system
prompt.
Update Metadata
Free-form key/value pairs you attach to the user — plan tier, feature flags, anything your own product needs alongside the platform's own fields.
curl -X POST https://api.travila.ai/api/v1/enduser/update-metadata \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"plan": "pro",
"onboardingComplete": "true"
}
}'
Response: empty body on success.
This is a merge, not a replace: keys you omit are left untouched. To delete a key, send it with an empty-string value:
{
"metadata": {"onboardingComplete": ""}
}
Related
- Authentication — Firebase tokens, secret keys, and
X-On-Behalf-Of - Model Routing — how
defaultGenerationConfiginteracts with per-turn overrides - Conversations — where the user context is applied
- Notifications — the separate subscriber record used for delivery