Outcome
Update the caller's language and/or timezone preference.
Before you start
- Verify the same intended caller and credential context before reading or updating preferences. No fixture credential or identity is usable live.
- The user must authorize the intended preference mutation. Record prior stored values and verify the unchanged planned input before executing.
Discover the action
topolo whoami --json
topolo actions get app_topolo_auth.my_i18n_preferences.update --detail --json
Input fields
| Field | Required | Meaning |
|---|---|---|
locale | Optional; at least one preference required | Locale query override on read; preferred persisted locale field on update. Region and language case are normalized. |
language | Optional; at least one preference required | Update alias for locale. A nonempty locale takes precedence when both are supplied. |
timezone | Optional; at least one preference required | Stored user timezone recognized by Intl, for example Europe/Paris or UTC. Omission preserves the stored timezone. |
Interpret the response
Supply at least one of locale, language or timezone. locale is the preferred language field; when both language fields are nonempty, locale wins. Locale normalization trims, converts underscores to hyphens and normalizes language/region case.
timezone is trimmed and accepted only when the runtime’s Intl formatter recognizes it. An invalid supplied locale or timezone is rejected before either repository write.
Only the supplied values appear in data: locale updates return locale and direction; timezone updates return timezone; combined updates return all three. Unrelated user metadata and omitted preferences are preserved.
The two database writes are not one transaction. A storage failure after the locale write can leave that value committed while timezone remains unchanged. Read the stored state, compare each intended value, and retry only the missing change; never treat a 500 as proof that nothing changed.
This mutation updates the current user’s preferences. It does not select another user, change organization membership, or update organization default language. AUTH_ERROR can also represent a credential-verification dependency failure and occurs before preference writes.
Worked requests and responses
Examples describe synthetic stored state. Headers shown for browser reads are part of that fixture; they are not action input fields. Resolve and use the current credential-scoped contract.
Locale-only update preserves timezone
Actual controller, normalization and repository SQL on minimal isolated SQLite; access-token verification and clock simulated.
Input
{
"locale": "ar-ae"
}
CLI: save the input as payload.json, then validate and plan the unchanged request. Confirm only an authorized mutation.
topolo actions validate app_topolo_auth.my_i18n_preferences.update --data @payload.json --json
topolo actions plan app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --json
topolo actions call app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --detail --json
MCP
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.my_i18n_preferences.update",
"input": {
"locale": "ar-ae"
},
"confirm": true
}
}
HTTP 200 response
{
"success": true,
"message": "I18n preferences updated",
"data": {
"locale": "ar-AE",
"direction": "rtl"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
Locale-only update preserves timezone. This is the disclosed synthetic fixture state, not a live user preference.
Stored fixture preferences after the write
{
"locale": "ar-AE",
"timezone": "Europe/Paris",
"updatedAt": "2026-10-05T00:00:00.000Z"
}
Language alias normalizes to a stored locale
Actual controller, normalization and repository SQL on minimal isolated SQLite; access-token verification and clock simulated.
Input
{
"language": "en_US"
}
CLI: save the input as payload.json, then validate and plan the unchanged request. Confirm only an authorized mutation.
topolo actions validate app_topolo_auth.my_i18n_preferences.update --data @payload.json --json
topolo actions plan app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --json
topolo actions call app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --detail --json
MCP
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.my_i18n_preferences.update",
"input": {
"language": "en_US"
},
"confirm": true
}
}
HTTP 200 response
{
"success": true,
"message": "I18n preferences updated",
"data": {
"locale": "en-US",
"direction": "ltr"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
Language alias normalizes to a stored locale. This is the disclosed synthetic fixture state, not a live user preference.
Stored fixture preferences after the write
{
"locale": "en-US",
"timezone": "Europe/Paris",
"updatedAt": "2026-10-05T00:00:00.000Z"
}
Timezone-only update preserves locale
Actual controller, normalization and repository SQL on minimal isolated SQLite; access-token verification and clock simulated.
Input
{
"timezone": " Asia/Makassar "
}
CLI: save the input as payload.json, then validate and plan the unchanged request. Confirm only an authorized mutation.
topolo actions validate app_topolo_auth.my_i18n_preferences.update --data @payload.json --json
topolo actions plan app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --json
topolo actions call app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --detail --json
MCP
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.my_i18n_preferences.update",
"input": {
"timezone": " Asia/Makassar "
},
"confirm": true
}
}
HTTP 200 response
{
"success": true,
"message": "I18n preferences updated",
"data": {
"timezone": "Asia/Makassar"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
Timezone-only update preserves locale. This is the disclosed synthetic fixture state, not a live user preference.
Stored fixture preferences after the write
{
"locale": "fr",
"timezone": "Asia/Makassar",
"updatedAt": "2026-10-05T00:00:00.000Z"
}
Both independently validated preferences are updated
Actual controller, normalization and repository SQL on minimal isolated SQLite; access-token verification and clock simulated.
Input
{
"locale": "ar-ae",
"timezone": "UTC"
}
CLI: save the input as payload.json, then validate and plan the unchanged request. Confirm only an authorized mutation.
topolo actions validate app_topolo_auth.my_i18n_preferences.update --data @payload.json --json
topolo actions plan app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --json
topolo actions call app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --detail --json
MCP
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.my_i18n_preferences.update",
"input": {
"locale": "ar-ae",
"timezone": "UTC"
},
"confirm": true
}
}
HTTP 200 response
{
"success": true,
"message": "I18n preferences updated",
"data": {
"locale": "ar-AE",
"direction": "rtl",
"timezone": "UTC"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
Both independently validated preferences are updated. This is the disclosed synthetic fixture state, not a live user preference.
Stored fixture preferences after the write
{
"locale": "ar-AE",
"timezone": "UTC",
"updatedAt": "2026-10-05T00:00:00.000Z"
}
Nonempty locale takes precedence over language alias
Actual controller, normalization and repository SQL on minimal isolated SQLite; access-token verification and clock simulated.
Input
{
"locale": "fr",
"language": "de"
}
CLI: save the input as payload.json, then validate and plan the unchanged request. Confirm only an authorized mutation.
topolo actions validate app_topolo_auth.my_i18n_preferences.update --data @payload.json --json
topolo actions plan app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --json
topolo actions call app_topolo_auth.my_i18n_preferences.update --data @payload.json --confirm --detail --json
MCP
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.my_i18n_preferences.update",
"input": {
"locale": "fr",
"language": "de"
},
"confirm": true
}
}
HTTP 200 response
{
"success": true,
"message": "I18n preferences updated",
"data": {
"locale": "fr",
"direction": "ltr"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
Nonempty locale takes precedence over language alias. This is the disclosed synthetic fixture state, not a live user preference.
Stored fixture preferences after the write
{
"locale": "fr",
"timezone": "Europe/Paris",
"updatedAt": "2026-10-05T00:00:00.000Z"
}
Verification
- Read my_i18n_preferences.get with no locale/lang override and the same credential. Compare data.userLocale and data.timezone against the intended normalized stored values.
Recovery
Read and retain the prior userLocale/timezone before an intentional change. Restore only previously non-null values with a separately reviewed update. There is no preference-clear field; do not invent a null reset. After uncertain writes, read and reconcile before serial retry.
Refusal and failure responses
Missing access credential is refused
HTTP 401
{
"timezone": "UTC"
}
{
"success": false,
"error": {
"message": "Access token required",
"code": "AUTH_ERROR",
"details": null,
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Verify the same intended identity/context and preserve the structured failure details. AUTH_ERROR alone does not prove expiry: this handler also maps credential-verification dependency failures to 401. If verification storage is unavailable, wait for recovery and repeat the unchanged read; use normal sign-in or credential refresh when credential expiry/refusal is established. Do not substitute another user’s credential.
Credential verification refusal is authoritative
HTTP 401
{
"timezone": "UTC"
}
{
"success": false,
"error": {
"message": "Synthetic expired credential",
"code": "AUTH_ERROR",
"details": null,
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Verify the same intended identity/context and preserve the structured failure details. AUTH_ERROR alone does not prove expiry: this handler also maps credential-verification dependency failures to 401. If verification storage is unavailable, wait for recovery and repeat the unchanged read; use normal sign-in or credential refresh when credential expiry/refusal is established. Do not substitute another user’s credential.
Credential-verification dependency failure also returns AUTH_ERROR before preference writes
HTTP 401
{
"timezone": "UTC"
}
{
"success": false,
"error": {
"message": "Synthetic credential-verification storage unavailable",
"code": "AUTH_ERROR",
"details": null,
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Verify the same intended identity/context and preserve the structured failure details. AUTH_ERROR alone does not prove expiry: this handler also maps credential-verification dependency failures to 401. If verification storage is unavailable, wait for recovery and repeat the unchanged read; use normal sign-in or credential refresh when credential expiry/refusal is established. Do not substitute another user’s credential.
Unavailable preference storage returns an error
HTTP 500
{
"timezone": "UTC"
}
{
"success": false,
"error": {
"message": "Synthetic preference read outage",
"code": "SERVER_ERROR",
"details": null,
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Read preferences with the same credential after storage recovers. Compare stored values and retry only the missing intended change; a combined write can partially commit.
Empty update is refused
HTTP 400
{}
{
"success": false,
"error": {
"message": "locale or timezone required",
"code": "VALIDATION_ERROR",
"details": {
"locale": "locale or timezone required"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Supply at least one declared preference field and correct the invalid locale/timezone. Validation refusal occurs before repository writes.
Malformed JSON is refused before writes
HTTP 400
{}
Raw fixture body: "{"
{
"success": false,
"error": {
"message": "locale or timezone required",
"code": "VALIDATION_ERROR",
"details": {
"locale": "locale or timezone required"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Supply at least one declared preference field and correct the invalid locale/timezone. Validation refusal occurs before repository writes.
Null body is refused before writes
HTTP 400
null
{
"success": false,
"error": {
"message": "locale or timezone required",
"code": "VALIDATION_ERROR",
"details": {
"locale": "locale or timezone required"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Supply at least one declared preference field and correct the invalid locale/timezone. Validation refusal occurs before repository writes.
Invalid locale is refused
HTTP 400
{
"locale": "../invalid"
}
{
"success": false,
"error": {
"message": "Invalid locale",
"code": "VALIDATION_ERROR",
"details": {
"locale": "Invalid locale"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Supply at least one declared preference field and correct the invalid locale/timezone. Validation refusal occurs before repository writes.
Valid locale plus invalid timezone commits neither field
HTTP 400
{
"locale": "ar-ae",
"timezone": "Mars/Olympus"
}
{
"success": false,
"error": {
"message": "Invalid timezone",
"code": "VALIDATION_ERROR",
"details": {
"timezone": "Invalid timezone"
},
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Supply at least one declared preference field and correct the invalid locale/timezone. Validation refusal occurs before repository writes.
First write failure leaves stored preferences unchanged
HTTP 500
{
"timezone": "UTC"
}
{
"success": false,
"error": {
"message": "Synthetic preference write outage",
"code": "SERVER_ERROR",
"details": null,
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "fr",
"timezone": "Europe/Paris"
}
Read preferences with the same credential after storage recovers. Compare stored values and retry only the missing intended change; a combined write can partially commit.
Second write failure can leave locale committed; read and reconcile before retry
HTTP 500
{
"locale": "ar-ae",
"timezone": "UTC"
}
{
"success": false,
"error": {
"message": "Synthetic preference write outage",
"code": "SERVER_ERROR",
"details": null,
"timestamp": "2026-10-05T00:00:00.000Z"
}
}
Stored fixture preferences after this response
{
"locale": "ar-AE",
"timezone": "Europe/Paris",
"updatedAt": "2026-10-05T00:00:00.000Z"
}
Read preferences with the same credential after storage recovers. Compare stored values and retry only the missing intended change; a combined write can partially commit.
Evidence and limits
Actual isolated preference handlers; no live user preference or credential was changed.
Actual dependencies:
- HTTP response serialization
- Locale precedence and normalization
- Timezone normalization through Intl
- Existing preference repository SQL evaluated by SQLite
Simulated dependencies:
- Access credential verification
- Fixed clock and synthetic fixture records
- Minimal users/organizations schema is not the complete migration database
- Deliberate storage failures