Manage user-specific settings including display preferences, themes, AI model configuration, and API keys.
GET /api/user-settings
Retrieve current user settings.
Response
UI theme preference: "light", "dark", or "system"Default: "system"
Color theme nameDefault: "better-auth"
Color mode: "light" or "dark"Default: "dark"
AI model for Ghost chatDefault: "auto"
Whether user is using their own OpenRouter API keyDefault: false
Masked OpenRouter API key (last 4 characters visible)Returns null if not set, or "****XXXX" format if set
Masked GitHub Personal Access Token (last 4 characters visible)Returns null if not set, or "****XXXX" format if set
Code syntax theme for light modeDefault: "vitesse-light"
Code syntax theme for dark modeDefault: "vitesse-black"
Code font familyDefault: "default"
Code font size in pixelsDefault: 13, Range: 8-32
Whether user has completed onboardingDefault: false
Last update timestamp (ISO 8601)
const response = await fetch('/api/user-settings');
const settings = await response.json();
console.log(settings);
/*
{
userId: "user_123",
displayName: "John Doe",
theme: "dark",
colorTheme: "better-auth",
colorMode: "dark",
ghostModel: "auto",
useOwnApiKey: false,
openrouterApiKey: null,
githubPat: "****abcd",
codeThemeLight: "vitesse-light",
codeThemeDark: "vitesse-black",
codeFont: "default",
codeFontSize: 13,
onboardingDone: true,
updatedAt: "2024-01-15T10:30:00Z"
}
*/
API keys are always masked in responses. To update them, you must provide the full key value in PATCH requests.
PATCH /api/user-settings
Update user settings. Only provided fields will be updated.
User’s display name (max 100 characters)
UI theme: "light", "dark", or "system"
Color theme name (max 50 characters)
Color mode: "light" or "dark"
AI model identifier (max 100 characters)Examples: "auto", "gpt-4", "claude-3-opus"
Enable/disable using own OpenRouter API key
OpenRouter API key (max 500 characters)Set to null to remove the key
GitHub Personal Access Token (max 500 characters)Set to null to remove the token
Code theme for light mode (max 100 characters)
Code theme for dark mode (max 100 characters)
Code font family (max 100 characters)
Code font size in pixels (8-32)
Mark onboarding as complete
Response
Returns the updated settings object with the same structure as GET.
const response = await fetch('/api/user-settings', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
theme: 'dark',
colorMode: 'dark',
codeThemeDark: 'github-dark'
})
});
const updated = await response.json();
console.log('Theme updated:', updated.theme);
Settings Validation
Field Constraints
| Field | Type | Min | Max | Default |
|---|
| displayName | string | - | 100 chars | - |
| theme | enum | - | - | “system” |
| colorTheme | string | - | 50 chars | ”better-auth” |
| colorMode | enum | - | - | “dark” |
| ghostModel | string | - | 100 chars | ”auto” |
| openrouterApiKey | string | - | 500 chars | null |
| githubPat | string | - | 500 chars | null |
| codeThemeLight | string | - | 100 chars | ”vitesse-light” |
| codeThemeDark | string | - | 100 chars | ”vitesse-black” |
| codeFont | string | - | 100 chars | ”default” |
| codeFontSize | number | 8 | 32 | 13 |
Theme Values
UI Theme (theme):
"light" - Always light mode
"dark" - Always dark mode
"system" - Follow system preference
Color Mode (colorMode):
"light" - Light color palette
"dark" - Dark color palette
AI Model Configuration
Ghost Model (ghostModel):
"auto" - Automatic model selection based on task
- Specific model IDs (e.g.,
"gpt-4", "claude-3-opus")
When useOwnApiKey is true, the specified ghostModel will be used with the user’s openrouterApiKey.
Usage Examples
Theme Switcher
async function setTheme(theme: 'light' | 'dark' | 'system') {
const response = await fetch('/api/user-settings', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ theme })
});
if (response.ok) {
// Apply theme to document
document.documentElement.setAttribute('data-theme', theme);
}
}
interface SettingsFormData {
displayName: string;
theme: 'light' | 'dark' | 'system';
codeFont: string;
codeFontSize: number;
}
async function saveSettings(data: SettingsFormData) {
try {
const response = await fetch('/api/user-settings', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
const updated = await response.json();
console.log('Settings saved:', updated);
return updated;
} catch (error) {
console.error('Failed to save settings:', error);
throw error;
}
}
API Key Management
async function configureCustomApiKey(apiKey: string | null) {
const response = await fetch('/api/user-settings', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
useOwnApiKey: apiKey !== null,
openrouterApiKey: apiKey
})
});
if (!response.ok) {
throw new Error('Failed to update API key');
}
return response.json();
}
// Enable custom key
await configureCustomApiKey('sk-or-v1-...');
// Disable custom key
await configureCustomApiKey(null);
Code Editor Settings
interface CodeEditorSettings {
font: string;
fontSize: number;
themeLight: string;
themeDark: string;
}
async function updateCodeEditor(settings: CodeEditorSettings) {
const response = await fetch('/api/user-settings', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
codeFont: settings.font,
codeFontSize: settings.fontSize,
codeThemeLight: settings.themeLight,
codeThemeDark: settings.themeDark
})
});
return response.json();
}
await updateCodeEditor({
font: 'Fira Code',
fontSize: 14,
themeLight: 'github-light',
themeDark: 'dracula'
});
Onboarding Flow
async function completeOnboarding(initialSettings: Partial<UserSettings>) {
const response = await fetch('/api/user-settings', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...initialSettings,
onboardingDone: true
})
});
if (response.ok) {
// Redirect to main app
window.location.href = '/dashboard';
}
}
await completeOnboarding({
displayName: 'John Doe',
theme: 'dark',
ghostModel: 'gpt-4'
});