Settings
Base path: /api/settings
GET /settings
Get all settings.
Auth: Admin
Response: Single settings object (id=1) with all configuration columns. Sensitive fields (smtp_pass, sumup_api_key, mollie_api_key, stripe_secret_key) are masked as "********" in the response.
PUT /settings
Update settings. Only fields present in the request body are updated.
Auth: Admin
Request body: Object with any combination of settable fields:
{
"salon_name": "Lumi Salon",
"salon_email": "info@salon.nl",
"salon_phone": "+31612345678",
"salon_address": "Kerkstraat 1",
"salon_postcode": "1234AB",
"salon_city": "Amsterdam",
"btw_number": "NL123456789B01",
"social_facebook": "https://facebook.com/salon",
"social_instagram": "https://instagram.com/salon",
"currency": "EUR",
"timezone": "Europe/Amsterdam",
"language": "nl",
"first_day_of_week": 1,
"time_format": "24h",
"new_client_extra_min": 15,
"calendar_slot_min": 15,
"calendar_buffer_min": 0,
"calendar_color_mode": "category",
"hide_unscheduled_staff": 0,
"dark_mode": "system",
"booking_mode": "direct",
"booking_clients": "all",
"booking_show_prices": 1,
"booking_show_duration": 1,
"booking_allow_staff_choice": 1,
"booking_min_lead_hours": 12,
"booking_max_future_days": 60
}
When purchase_orders_enabled is set to 0, internal_consumption_enabled is also automatically set to 0. Setting internal_consumption_enabled to 1 while purchase_orders_enabled is 0 returns a 400 error.
POST /settings/upload-logo
Upload a salon logo image.
Auth: Admin
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
logo | file | Yes | Image file (JPEG, PNG, GIF, SVG, WebP; max 5MB) |
Response: { "url": "/uploads/logo-1234567890.png" }
POST /settings/test-email
Send a test email to verify SMTP configuration.
Auth: Admin
Request body: { "to": "test@example.com" }
Response: { "ok": true } or 400 with error message if SMTP fails.
Locations
Locations are managed through a separate route at /api/locations (not under /api/settings).
GET /locations
Get all locations. Supports ?active=true to filter active ones only.
Auth: Logged in
GET /locations/:id
Get a single location.
Auth: Logged in
POST /locations
Create a new location.
Auth: Admin
| Field | Type | Required |
|---|---|---|
name | string | Yes |
address | string | No |
phone | string | No |
email | string | No |
is_default | boolean | No |
PUT /locations/:id
Update a location.
Auth: Admin
DELETE /locations/:id
Delete a location.
Auth: Admin
Holidays
GET /settings/holidays
Get holidays ordered by date.
POST /settings/holidays
Add a holiday.
| Field | Type | Required |
|---|---|---|
name | string | Yes |
date | string (YYYY-MM-DD) | Yes |
DELETE /settings/holidays/:id
Delete a holiday.
Opening Hours
Opening hours are configured as part of the initial setup (POST /api/auth/setup) and are stored in the opening_hours table. There is no separate REST endpoint to update opening hours after setup; changes are applied by updating the database directly or via a future admin settings screen.
VAT Rates
GET /settings/vat-rates
Get all VAT rates.
POST /settings/vat-rates
Create a new VAT rate.
| Field | Type | Required | Default |
|---|---|---|---|
name | string | Yes | — |
percentage | number | Yes | — |
is_default | 0/1 | No | 0 |
is_disabled | 0/1 | No | 0 |
When is_default: 1 is set, all other rates have is_default cleared.
PUT /settings/vat-rates/:id
Update a VAT rate.
DELETE /settings/vat-rates/:id
Delete a VAT rate.
Feature Toggles
Optional features are controlled via columns in the settings table. All are stored as integers (0/1) or numbers.
General Toggles
| Setting | Type | Default | Description |
|---|---|---|---|
swipe_actions_enabled | boolean | 1 | Swipe gestures (mobile) |
haptic_feedback_enabled | boolean | 1 | Haptic feedback (mobile) |
longpress_contact_enabled | boolean | 1 | Long-press to call/message a client (mobile) |
offline_mode_enabled | boolean | 1 | Offline caching for the mobile app |
staff_personal_agenda_enabled | boolean | 1 | Staff members see only their own agenda |
Inventory & Purchasing
| Setting | Type | Default | Description |
|---|---|---|---|
purchase_orders_enabled | boolean | 1 | Purchase orders and suppliers tabs. When false, quantity_on_hand becomes directly editable on product forms |
internal_consumption_enabled | boolean | 1 | Internal consumption tab. Requires purchase_orders_enabled = true; enabling internal consumption while purchase orders are disabled returns a 400 error |
Dashboard & Analytics
| Setting | Type | Default | Description |
|---|---|---|---|
daily_revenue_goal_enabled | boolean | 0 | Show daily revenue goal on dashboard |
daily_revenue_goal | number | 0 | Target daily revenue amount |
revenue_forecast_enabled | boolean | 0 | Revenue forecast widget |
visit_frequency_alerts_enabled | boolean | 0 | Alert when clients haven't visited in a while |
visit_frequency_days | integer | 42 | Days threshold for visit frequency alerts |
CRM & Marketing
| Setting | Type | Default | Description |
|---|---|---|---|
followup_sequences_enabled | boolean | 0 | Automated follow-up email sequences after bookings |
product_recommendations_enabled | boolean | 0 | Product recommendations for clients |
giftcard_qr_enabled | boolean | 0 | QR codes on gift card emails |
seasonal_promotions_enabled | boolean | 0 | Seasonal promotions automation |
bulk_campaigns_enabled | boolean | 0 | Bulk email campaigns |
waitlist_position_enabled | boolean | 0 | Show waitlist position to clients |
Notifications
| Setting | Type | Default | Description |
|---|---|---|---|
notify_salon_waitlist_join | boolean | 1 | Notify salon when someone joins the waitlist |
notify_salon_slot_available | boolean | 1 | Notify salon when a slot becomes available |
noshow_email_enabled | boolean | 1 | Send email when a no-show fee is charged |
low_stock_email_enabled | boolean | 1 | Send email on low stock alert |
booking_request_notify_email | boolean | 1 | Send email to admin when a provisional booking request arrives |
booking_request_notify_push | boolean | 1 | Send push notification to admin when a provisional booking request arrives |
No-Show Fees
| Setting | Type | Default | Description |
|---|---|---|---|
noshow_enabled | boolean | 0 | Enable automatic no-show fee charging via Stripe |
noshow_fee_type | string | percentage | percentage or fixed |
noshow_fee_value | number | 50 | Percentage (0-100) or fixed amount |
noshow_min_amount | number | 0 | Minimum no-show fee (for percentage type) |
Payment Integrations
| Setting | Type | Description |
|---|---|---|
sumup_api_key | string | SumUp API key (masked in GET response) |
sumup_merchant_code | string | SumUp merchant code |
sumup_reader_id | string | SumUp card reader ID |
mollie_api_key | string | Mollie API key (masked in GET response) |
mollie_terminal_id | string | Mollie terminal ID |
stripe_secret_key | string | Stripe secret key (masked in GET response) |
stripe_webhook_secret | string | Stripe webhook signing secret |
Loyalty Settings
Loyalty programme configuration is managed via a separate route at /api/loyalty. The loyalty_enabled flag is stored in the loyalty_settings table, not in the main settings row.
GET /loyalty/settings
Get loyalty settings including loyalty_enabled, points per euro, and reward thresholds.
PUT /loyalty/settings
Update loyalty settings.
Auth: Admin