Preferences API
The Preferences API allows users to read and update their personal application preferences, including notification settings, AI behavior, view modes, and localization.
Most fields are flat. Two are not: quietHours, which is freeform, and aiCustomInstructions, which is a structured object with its own merge rules — see Custom Instructions.
Custom Instructions
Custom Instructions are account-wide, user-authored preferences that Chao considers on every eligible turn. They are explicit standing instructions — not memory, not agent instructions, and not retrieved knowledge. They apply to every conversation until you change them.
| Field | Type | Default | Description |
|---|---|---|---|
aiCustomInstructionsEnabled | boolean | false | Master switch. When false, no custom-instruction text is added to any prompt, even if values are stored. |
aiCustomInstructions | object | null | null | Structured instruction object, or null when unset. |
aiCustomInstructions.aboutUser | string | null | null | Stable context about you that Chao should keep in mind. |
aiCustomInstructions.responsePreferences | string | null | null | How you want Chao to communicate — length, format, tone, workflow. |
Limits
| Rule | Value |
|---|---|
| Per field | 2,000 characters |
| Both fields combined | 4,000 characters |
| Allowed control characters | newline and tab only |
Characters are counted as Unicode code points, not UTF-16 code units. An emoji such as 😀 counts as one character even though many programming languages report its length as 2. If you are validating input in a client before submitting, count with a code-point-aware method or you will reject text this API accepts.
Values are normalized before the limit is applied: Unicode is converted to NFC, \r\n and \r become \n, and leading and trailing whitespace is trimmed. Interior whitespace, Markdown, code blocks, and non-Latin scripts are preserved exactly as written.
Because both fields are capped at 2,000, the 4,000 combined limit cannot currently be exceeded on its own. It exists for forward compatibility and will apply if further instruction fields are added.
Update semantics
aiCustomInstructions merges at the subfield level. Omitting a subfield preserves it; sending explicit null clears it.
| Request body | Result |
|---|---|
aiCustomInstructions omitted | Stored value unchanged |
"aiCustomInstructions": null | Both fields cleared |
{"aboutUser": "..."} | aboutUser updated, responsePreferences preserved |
{"aboutUser": null} | aboutUser cleared, responsePreferences preserved |
| Both fields empty or null | Whole object stored as null |
Edits take effect on your next message. There is no cache delay to wait out.
How Chao treats them
Custom Instructions are compiled into a clearly delimited section of the prompt, marked as lower authority than platform policy, Chao's own identity, tool governance, and agent or twin instructions. Chao follows them wherever they do not conflict with those higher-priority rules.
In practice this means instructions like these work as expected:
Call me Ömer.Prefer concise answers unless I ask for depth.I am a computer engineer; skip basic programming explanations.Use tables when comparing alternatives.
Instructions that attempt to override platform behavior are ignored for the conflicting part only — the rest of what you wrote still applies. Custom Instructions cannot rename Chao, disable tool approval, grant capabilities your plan does not include, or cause hidden system prompts to be disclosed.
Example
curl -X PATCH https://api.chainabit.com/api/v1/preferences \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"aiCustomInstructionsEnabled": true,
"aiCustomInstructions": {
"aboutUser": "I am a computer engineer working mostly in TypeScript.",
"responsePreferences": "Be concise. Use tables for comparisons."
}
}'Turning the feature off without discarding what you wrote:
curl -X PATCH https://api.chainabit.com/api/v1/preferences \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "aiCustomInstructionsEnabled": false }'Privacy
Instruction text is never written to application logs, metrics labels, or analytics events. Diagnostics record only whether the feature is enabled, an estimated token cost, whether the text was truncated, and a one-way content hash. Instructions are scoped to your account and are never visible to other users.
AI Message Suggestion Preferences
Chainabit also stores two AI-session continuation preferences on the identity profile:
| Field | Type | Default | Description |
|---|---|---|---|
aiMessageSuggestionsEnabled | boolean | true | When true, completed assistant messages may include structured follow-up suggestion cards. |
aiMessageSuggestionCount | integer | 3 | Number of suggestion cards to generate per completed assistant message. Allowed range: 2–4. |
When aiMessageSuggestionsEnabled is false, assistant messages return suggestions: [] and no message.suggestions SSE event is emitted with cards for that message.
Example Patch
curl -X PATCH https://api.chainabit.com/api/v1/preferences \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"aiMessageSuggestionsEnabled": true,
"aiMessageSuggestionCount": 4
}'Base Path
https://api.chainabit.com/api/v1/preferencesAuthentication
All Preferences endpoints require a valid JWT Bearer token.
Environment Variables
Set these variables before running any example on this page:
export BASE_URL="https://api.chainabit.com/api/v1"
export TOKEN="your-access-token"Get your access token by calling POST /auth/login.
Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /preferences | Get user preferences | JWT | 60/min |
| PATCH | /preferences | Update user preferences | JWT | 30/min |
GET /preferences
Description
Retrieve the current user's full preference object.
Authentication: JWT Bearer token required. Rate limit: 60 requests per minute.
Request
No request body. No path or query parameters.
Response
{
"data": {
"emailNotifications": true,
"pushNotifications": true,
"friendActivityAlerts": true,
"aiCoachingReminders": true,
"countryCode": "TR",
"languageCode": "en",
"discoverableByEmail": false,
"allowAiTraining": false,
"aiMemoryEnabled": true,
"aiMessageSuggestionsEnabled": true,
"aiMessageSuggestionCount": 3,
"aiCustomInstructionsEnabled": true,
"aiCustomInstructions": {
"aboutUser": "I am a computer engineer working mostly in TypeScript.",
"responsePreferences": "Be concise. Use tables for comparisons."
},
"aiPersona": "supportive",
"preferredAiProvider": "google",
"chainViewMode": "snake",
"bitViewMode": "kanban",
"keyboardShortcutsEnabled": true,
"notificationDigestFrequency": "daily",
"notificationFrequencyMode": "balanced",
"quietHours": {},
"timezone": "Europe/Istanbul",
"useLatestSessions": true,
"hideTitles": false,
"updatedAt": "2026-03-17T10:00:00.000Z"
},
"meta": null,
"error": null
}Response Fields
| Field | Type | Description |
|---|---|---|
emailNotifications | boolean | Email notifications enabled |
pushNotifications | boolean | Push notifications enabled |
friendActivityAlerts | boolean | Notifications for friend activity enabled |
aiCoachingReminders | boolean | AI coaching reminder notifications enabled |
countryCode | string | ISO country code (max 2 characters) |
languageCode | string | Display language code (max 10 characters) |
discoverableByEmail | boolean | Whether the account can be found by email lookup |
allowAiTraining | boolean | Whether user content may be used for AI training |
aiMemoryEnabled | boolean | Whether the AI may persist memory across sessions |
aiMessageSuggestionsEnabled | boolean | Follow-up suggestion cards enabled |
aiMessageSuggestionCount | integer | Number of suggestion cards per message (2–4) |
aiCustomInstructionsEnabled | boolean | Whether Custom Instructions are applied to prompts |
aiCustomInstructions | object | null | { aboutUser, responsePreferences }, or null when unset |
aiPersona | string | AI persona; see AiPersona values |
preferredAiProvider | string | google, openai, anthropic, or mistral |
chainViewMode | string | See ChainViewMode values |
bitViewMode | string | See BitViewMode values |
keyboardShortcutsEnabled | boolean | Keyboard shortcuts enabled in the web app |
notificationDigestFrequency | string | realtime, daily, or weekly |
notificationFrequencyMode | string | minimal, balanced, or frequent |
quietHours | object | Freeform quiet-hours configuration |
timezone | string | IANA timezone string (max 64 characters) |
useLatestSessions | boolean | Whether the UI resumes the most recent AI session by default |
hideTitles | boolean | Whether titles are hidden in list views |
updatedAt | string | ISO 8601 timestamp of last update |
Code Examples
curl https://api.chainabit.com/api/v1/preferences \
-H "Authorization: Bearer $TOKEN"const response = await fetch(`${BASE_URL}/preferences`, {
headers: {
'Authorization': `Bearer ${TOKEN}`,
},
});
const data = await response.json();import requests
response = requests.get(
f"{BASE_URL}/preferences",
headers={"Authorization": f"Bearer {TOKEN}"},
)
data = response.json()PATCH /preferences
Description
Update one or more preference fields. Only the fields included in the request body are updated; all other fields remain unchanged (partial update). The request body is validated with a strict allowlist — any field not in the schema below (for example a legacy theme or nested notifications field) is rejected with 400 Bad Request (property theme should not exist).
Authentication: JWT Bearer token required. Rate limit: 30 requests per minute.
Request
All fields are optional. Only include the fields you want to change.
| Field | Type | Constraints | Description |
|---|---|---|---|
emailNotifications | boolean | — | Enable email notifications |
pushNotifications | boolean | — | Enable push notifications |
friendActivityAlerts | boolean | — | Enable friend activity notifications |
aiCoachingReminders | boolean | — | Enable AI coaching reminder notifications |
countryCode | string | max length 2 | ISO country code |
languageCode | string | max length 10 | Display language code |
discoverableByEmail | boolean | — | Allow lookup of this account by email |
allowAiTraining | boolean | — | Allow user content to be used for AI training |
aiMemoryEnabled | boolean | — | Allow the AI to persist memory across sessions |
aiMessageSuggestionsEnabled | boolean | — | Enable follow-up suggestion cards |
aiMessageSuggestionCount | integer | 2–4 | Number of suggestion cards per message |
aiCustomInstructionsEnabled | boolean | — | Master switch for Custom Instructions |
aiCustomInstructions | object | null | 2,000 characters per field, 4,000 combined | Structured account-wide instructions. Merges at the subfield level; explicit null clears. See Custom Instructions |
aiPersona | string (enum) | see AiPersona values | AI persona |
preferredAiProvider | string (enum) | google, openai, anthropic, mistral | Non-google values require a paid entitlement; FREE-plan accounts are silently kept on google |
chainViewMode | string (enum) | see ChainViewMode values | The legacy value sneak is still accepted and silently mapped to snake for backward compatibility with old clients |
bitViewMode | string (enum) | see BitViewMode values | Default bit view |
keyboardShortcutsEnabled | boolean | — | Enable keyboard shortcuts |
notificationDigestFrequency | string (enum) | realtime, daily, weekly | Digest email frequency |
notificationFrequencyMode | string (enum) | minimal, balanced, frequent | minimal suppresses normal-priority reminders; frequent sends all |
quietHours | object | freeform | Quiet-hours configuration |
timezone | string | max length 64 | IANA timezone, e.g. Europe/Istanbul |
useLatestSessions | boolean | — | Resume the most recent AI session by default |
hideTitles | boolean | — | Hide titles in list views |
AiPersona values
supportive, motivational, analytical, challenger, strategist, stoic, witty, socratic, pragmatist, ruthless
ChainViewMode values
consistency, puzzle, snake, sheet, voxel
BitViewMode values
matrix, kanban, calendar, gantt, tree, singularity
Response
{
"data": {
"emailNotifications": true,
"pushNotifications": true,
"friendActivityAlerts": true,
"aiCoachingReminders": false,
"countryCode": "TR",
"languageCode": "en",
"discoverableByEmail": false,
"allowAiTraining": false,
"aiMemoryEnabled": true,
"aiMessageSuggestionsEnabled": true,
"aiMessageSuggestionCount": 3,
"aiPersona": "analytical",
"preferredAiProvider": "google",
"chainViewMode": "snake",
"bitViewMode": "kanban",
"keyboardShortcutsEnabled": true,
"notificationDigestFrequency": "weekly",
"notificationFrequencyMode": "minimal",
"quietHours": {},
"timezone": "Europe/Istanbul",
"useLatestSessions": true,
"hideTitles": false,
"updatedAt": "2026-03-17T11:00:00.000Z"
},
"meta": null,
"error": null
}Response Fields
Returns the full updated preferences object. See GET /preferences for the complete field reference.
Code Examples
curl -X PATCH https://api.chainabit.com/api/v1/preferences \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"aiCoachingReminders": false,
"aiPersona": "analytical",
"notificationDigestFrequency": "weekly",
"notificationFrequencyMode": "minimal"
}'const response = await fetch(`${BASE_URL}/preferences`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
aiCoachingReminders: false,
aiPersona: 'analytical',
notificationDigestFrequency: 'weekly',
notificationFrequencyMode: 'minimal',
}),
});
const data = await response.json();import requests
response = requests.patch(
f"{BASE_URL}/preferences",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"aiCoachingReminders": False,
"aiPersona": "analytical",
"notificationDigestFrequency": "weekly",
"notificationFrequencyMode": "minimal",
},
)
data = response.json()Notes
- Partial Updates: The
PATCHendpoint only updates the fields included in the request body; all other fields are left unchanged. Flat fields are set directly on the preferences record.aiCustomInstructionsis the one field that merges: omitting a subfield preserves its stored value, and sending explicitnullclears it. See Update semantics. - Strict Validation: The request body is validated against an allowlist. Sending any field that isn't part of
UpdatePreferencesDto(for example a legacythemefield or a nestednotificationsobject) returns400 Bad Requestwith a message likeproperty theme should not exist. This is intended behavior, not a bug — always check this page for the current field list rather than relying on older integration code. - Defaults: New users receive a default preference set. The GET endpoint always returns the full preference object.
chainViewModeLegacy Alias: The old valuesneakis still accepted onPATCHand is silently mapped tosnakefor backward compatibility with older clients;GETalways returns the current value (snake).preferredAiProviderEntitlement Gating: SettingpreferredAiProviderto anything other thangooglerequires a paid entitlement. FREE-plan accounts that attempt to set a non-googleprovider are silently kept ongooglerather than receiving an error.- Validation: Invalid values (e.g., an unsupported enum value, an out-of-range
aiMessageSuggestionCount, or acountryCode/languageCode/timezonethat exceeds its max length) return400 Bad Requestwith a validation error. - Custom Instruction Validation: Instruction text that exceeds 2,000 characters in a field, exceeds 4,000 combined, or contains control characters other than newline and tab returns
400 Bad Request. BecausePATCHis validated as a whole, a rejected instruction field also rejects every other field in the same request — validate instruction length client-side before submitting rather than relying on the round trip. - Side Effects: Changing
timezoneaffects how scheduled chain reminders are calculated. Changes take effect on the next scheduled event.