Settings reference
Generated from contracts/settings.schema.json by node docs/gen.mjs. Do not edit by hand.
One JSON document per app. Read and write it in the dashboard (Settings) or with GET and PUT /v1/app/settings (see the settings overview). Every key has a default, so {} is valid. Unknown keys are refused. A PUT replaces the whole document and needs If-Match with the current version.
Cross-field rules: role keys are unique; every role named in default_role, guests.role, policy, limits, onboarding.slides and calls.per_minute.payer_role exists in roles; each from/to pair appears once in policy; i18n.default_lang is one of i18n.languages. The settings overview explains them.
Top level
Section titled “Top level”| Key | Type | Default | Meaning |
|---|---|---|---|
default_role |
role key | "member" |
Role when the token has none. |
version |
1 |
1 |
Settings schema version. |
branding
Section titled “branding”Look of the chat.
How the chat looks. The colours, the font and the corner radius apply everywhere the chat shows.
Primary is the colour of buttons, and on_primary is the text drawn on it, so pick a pair with enough contrast. Background, surface and text colour the chat’s page, its cards and its words. A web font needs font_css_url, the stylesheet that defines it.
| Key | Type | Default | Meaning |
|---|---|---|---|
branding.avatars |
boolean | true |
Show the people’s pictures; off draws the initial on the primary colour. |
branding.background |
color #rrggbb |
"#ffffff" |
Background color. |
branding.font_css_url |
https URL or null | null |
Stylesheet that loads the font. |
branding.font_family |
string, up to 200 chars | "system-ui, sans-serif" |
CSS font family. |
branding.heading_font_family |
string, up to 200 chars or null | null |
CSS font family for headings; null uses font_family. |
branding.inbox_close_chip |
boolean | false |
The inbox close button is a 30px tinted chip. |
branding.inbox_tint |
boolean | false |
Tint the inbox background on every layout, not only two-pane. |
branding.launcher.enabled |
boolean | true |
Show the launcher bubble. |
branding.launcher.inline_close_desktop |
boolean | true |
Show the inbox close button on the inline page on a screen with a mouse. |
branding.launcher.plain |
boolean | false |
Flat bubble colour, and Arial in the unread badge and the toast. |
branding.launcher.position |
"right", "left" |
"right" |
Launcher corner. |
branding.logo_url |
https URL or null | null |
Logo image. |
branding.on_primary |
color #rrggbb |
"#ffffff" |
Text on the primary color. |
branding.primary |
color #rrggbb |
"#2f6fed" |
Primary color. |
branding.radius_px |
integer, 0 to 32 | 12 |
Corner radius in pixels. |
branding.surface |
color #rrggbb |
"#f4f5f7" |
Surface color. |
branding.text |
color #rrggbb |
"#111318" |
Text color. |
Video calls.
| Key | Type | Default | Meaning |
|---|---|---|---|
calls.area |
string ^[A-Z_]{2,32}$ |
"GLOBAL" |
Agora area code, e.g. EUROPE. |
calls.codec |
"vp8", "vp9", "h264", "av1" |
"vp8" |
Agora video codec. |
calls.confirm_start |
boolean | false |
Ask before starting a call. |
calls.dock |
boolean | false |
Every call takes the whole window, the chat button docks the call as a strip or a side rail beside the thread, and a minimise button shrinks it to a bubble. |
calls.enabled |
boolean | false |
Video calls enabled. |
calls.max_duration_minutes |
integer, 1 to 1440 | 240 |
Calls end after this. |
calls.offline_note |
boolean | false |
After calling an offline peer, note in the thread that they may not answer. |
calls.per_minute.currency |
ISO 4217 code | "EUR" |
ISO 4217 currency. |
calls.per_minute.enabled |
boolean | false |
Charge calls per minute. |
calls.per_minute.payer_role |
role key or null | null |
Role that pays; null is the caller. |
calls.per_minute.preauthorize_minutes |
integer, 1 to 60 | 1 |
Minutes charged when both have joined. |
calls.per_minute.price_minor |
integer, at least 0 | 0 |
Price per minute in minor units. |
calls.ring_timeout_seconds |
integer, 10 to 600 | 45 |
Ring time before missed. |
calls.ring_when_closed |
boolean | true |
An incoming call opens the chat and rings while it is closed; off shows the toast and sound, and the ring when the chat opens. |
calls.show_locked |
boolean | false |
Show buyers a locked call button with a tip when they cannot call a seller. |
calls.single_card |
boolean | false |
One card per call that reads Call ended when it is over, and a busy callee closes the caller’s call screen instead of showing an error. |
calls.video.fps |
integer, 1 to 60 | 30 |
Capture frame rate. |
calls.video.height |
integer, 120 to 2160 | 720 |
Capture height in pixels. |
calls.video.width |
integer, 160 to 3840 | 1280 |
Capture width in pixels. |
calls.warn_if_offline |
boolean | true |
Warn before calling an offline peer. |
Messages.
| Key | Type | Default | Meaning |
|---|---|---|---|
chat.avatar_upload |
boolean | false |
Users may upload their own picture; it shows only after photo moderation approves it, and it wins over the host’s avatar. |
chat.consent_allow_last |
boolean | false |
The photo consent prompt puts Don’t allow before Allow; off puts Allow first. |
chat.earlier_on_scroll |
boolean | false |
Older messages load when the thread scrolls to its top; off shows a Load earlier button. |
chat.edit_window_seconds |
integer, 0 to 31536000 | 172800 |
Senders may edit a text message this long after sending; 0 turns edits off. |
chat.photo_max_bytes |
integer, 1 to 50000000 | 20000000 |
Maximum photo upload size in bytes. |
chat.photos |
boolean | true |
Photo messages enabled. |
chat.refuse_blocked_send |
boolean | false |
A message to someone who blocked the sender is refused (403 forbidden, reason undelivered) and not stored; off accepts it and drops it silently. |
chat.short_day_labels |
boolean | false |
Day separators read day and month only; off adds the weekday, and the year for earlier years. |
chat.text_max_length |
integer, 1 to 10000 | 4000 |
Maximum message length. |
chat.unsend_window_seconds |
integer, 0 to 31536000 | 172800 |
Senders may delete a message for everyone this long after sending; 0 turns it off. Deleting for oneself is always allowed. |
chat.video_max_bytes |
integer, 1000000 to 209715200 | 104857600 |
Maximum video size in bytes; the browser converts to fit. |
chat.video_max_seconds |
integer, 1 to 3600 | 900 |
Maximum video length in seconds. |
chat.videos |
boolean | true |
Video messages enabled. |
chat.view_once |
boolean | true |
Senders may mark a photo, video or voice note view once. |
chat.voice |
boolean | true |
Voice notes enabled. |
chat.voice_max_bytes |
integer, 100000 to 52428800 | 10485760 |
Maximum voice note size in bytes. |
chat.voice_max_seconds |
integer, 1 to 3600 | 900 |
Maximum voice note length in seconds. |
commerce
Section titled “commerce”Selling services.
| Key | Type | Default | Meaning |
|---|---|---|---|
commerce.catalog_source |
"webhook", "dashboard" |
"webhook" |
Catalog from the catalog callout or the dashboard. |
commerce.checkout_url_template |
https URL or null | null |
Checkout page; placeholders {purchase_id} {item_id} {amount_minor} {currency} {seller_external_id} {buyer_external_id} {lang} {return_url}. |
commerce.custom_amount.enabled |
boolean | false |
Buyers may type an amount, such as a tip. |
commerce.custom_amount.max_minor |
integer, at least 1 | 100000 |
Largest amount in minor units. |
commerce.custom_amount.min_minor |
integer, at least 1 | 100 |
Smallest amount in minor units. |
commerce.enabled |
boolean | false |
Selling in conversations enabled. |
commerce.payment_mode |
"server_charge", "page_callback", "checkout_url" |
"server_charge" |
How purchases are paid. |
commerce.payment_window |
"popup", "same_tab" |
"popup" |
Where checkout and top-up pages open. |
commerce.show_balance |
boolean | false |
Show the balance the catalog callout returns. |
commerce.topup_url_template |
https URL or null | null |
Top-up page for the purchase that ran short; same placeholders as checkout. |
commerce.trim_whole_prices |
boolean | false |
A euro price on a purchase card reads 5 € or 5.50 €, never grouped. |
guests
Section titled “guests”Anonymous guests.
Guests get a random id guest:<uuid> and the role in guests.role. The host page keeps their token in localStorage; see the security model.
| Key | Type | Default | Meaning |
|---|---|---|---|
guests.enabled |
boolean | false |
Allow anonymous guests. |
guests.merge_on_login |
boolean | true |
Merge a recent guest into the member who logs in on that browser. |
guests.name_prefix |
string, up to 32 chars | "Guest" |
Guests show as “<prefix> <6 chars>”. |
guests.notify_prompt |
boolean | false |
While the viewer’s own message is the last one, offer to tell them when the peer replies; tapping sends navigate reason notify for the host to collect an address. Same viewers as the nudge. |
guests.notify_prompt_signup |
boolean | false |
The notify prompt’s button reads Create account and opens guests.nudge.signup_url (navigate reason signup) instead of Notify me; with no signup_url it stays Notify me. |
guests.notify_prompt_sticky |
boolean | false |
Keep the notify prompt once the conversation has a message, until the viewer taps or dismisses it, instead of only while their own message is the last one. |
guests.nudge.enabled |
boolean | true |
Show guests a create-account nudge, and members whose role is guests.role when that is not default_role. |
guests.nudge.login_url |
https URL or null | null |
Where the nudge sends to log in; placeholder {lang}. |
guests.nudge.signup_url |
https URL or null | null |
Where the nudge sends to sign up; placeholder {lang}. |
guests.role |
role key | "member" |
Role given to guests. |
Languages and copy.
| Key | Type | Default | Meaning |
|---|---|---|---|
i18n.copy |
map of "it", "en", "es", "fr" to map of key to string |
{} |
Text overrides per language and message key. |
i18n.default_lang |
"it", "en", "es", "fr" |
"en" |
Language when nothing else decides. |
i18n.languages |
list of "it", "en", "es", "fr" |
["en"] |
Languages offered. |
limits
Section titled “limits”Limits per role.
One entry per role. A role with no entry gets the defaults shown on each key. null means unlimited.
Counts run over the last 24 hours, except messages per minute. A user who reaches a cap is refused with limit_reached and the reason names the cap. messages_to_unanswered stops a sender from piling up messages before the peer has ever replied, which is the usual guard against unwanted messages to a seller. A perms claim in the client token replaces any of these for that one user.
| Key | Type | Default | Meaning |
|---|---|---|---|
limits |
map of role key to object | {"member":{"messages_per_minute":30,"messages_to_unanswered":null,"new_conversations_per_day":null,"photos_per_day":null,"voice_per_day":100}} |
Limits per role. |
limits.<role>.messages_per_minute |
integer, at least 1 or null | 30 |
Messages per minute; null is unlimited. |
limits.<role>.messages_to_unanswered |
integer, at least 0 or null | null |
Messages before the peer first replies; null is unlimited. |
limits.<role>.new_conversations_per_day |
integer, at least 0 or null | null |
New conversations per 24h; null is unlimited. |
limits.<role>.photos_per_day |
integer, at least 0 or null | null |
Photos and videos per 24h; null is unlimited. |
limits.<role>.voice_per_day |
integer, at least 0 or null | 100 |
Voice notes per 24h; null is unlimited. |
Links to the client’s site.
| Key | Type | Default | Meaning |
|---|---|---|---|
links.open_conversation_url_template |
https URL or null | null |
Page that opens a conversation; placeholders {peer_external_id} {lang}. |
links.profile_sellers_only |
boolean | false |
The conversation header opens the profile of sellers only. |
links.profile_url_template |
https URL or null | null |
Profile page; placeholders {external_id} {lang}; null lets the host resolve. |
links.services_menu |
boolean | true |
With services_page, buyers also get a Services menu item. |
links.services_page |
boolean | false |
Buyers can open the seller’s services page from a conversation. |
links.services_url_template |
https URL or null | null |
Services page; placeholders {external_id} {lang}; null lets the host resolve. |
links.support_email |
email or null | null |
Shown to banned users. |
moderation
Section titled “moderation”Moderation.
We host adult content. Sex, nudity, erotic talk and selling sexual content between adults are allowed: the classifiers never flag them and there is no category for them. They look only for the categories below: text and photos after delivery, and calls through Agora’s moderation when moderation.calls.agora is on, with the peer sampling a participant whose Agora task did not start when moderation.calls.fallback_capture is on.
Minors: any sign a participant is or could be under age, or any request for content of minors.Trafficking: a third party controls a participant (a “manager” or “owner”, debts paid with sex work, no control of their own money or papers).NonConsent: sextortion and sexual coercion, including threats to show or publish images, and pressing on after a clear no. Agreed role play between adults is not this.Violence,SelfHarm,Hate,Harassment,Doxxing,Fraud: what the names say.IllegalServices,Weapons,Drugs: selling or showing them.ExternalPayment: the parties agree that money for the service changes hands outside your site. A business rule, not a safety one.
Actions: off ignores the category. alert opens a case in the dashboard, sends moderation.flagged, and fires alert rules; the content stays delivered. hold also hides the message or photo from the peer until a moderator acts. block also removes the message or rejects the photo. Every category defaults to alert, and a finding in a category with no key here (some photo categories) acts as alert, so nothing is hidden until you choose to. Most adult sites set Minors, Trafficking and NonConsent to block.
| Key | Type | Default | Meaning |
|---|---|---|---|
moderation.calls.agora |
boolean | true |
Agora call moderation enabled. |
moderation.calls.capture_interval_seconds |
integer, 5 to 300 | 10 |
Seconds between frames a peer captures. |
moderation.calls.capture_max_frames |
integer, 1 to 360 | 30 |
Frames captured per participant per call. |
moderation.calls.fallback_capture |
boolean | true |
Peers sample a participant whose Agora task did not start. |
moderation.photos |
boolean | true |
Moderate photos. |
moderation.rules.Alcohol |
"off", "alert", "hold", "block" |
"alert" |
Action for Alcohol findings. |
moderation.rules.Animals |
"off", "alert", "hold", "block" |
"alert" |
Action for Animals findings. |
moderation.rules.Doxxing |
"off", "alert", "hold", "block" |
"alert" |
Action for Doxxing findings. |
moderation.rules.Drugs |
"off", "alert", "hold", "block" |
"alert" |
Action for Drugs findings. |
moderation.rules.ExternalPayment |
"off", "alert", "hold", "block" |
"alert" |
Action for ExternalPayment findings. |
moderation.rules.Fraud |
"off", "alert", "hold", "block" |
"alert" |
Action for Fraud findings. |
moderation.rules.Harassment |
"off", "alert", "hold", "block" |
"alert" |
Action for Harassment findings. |
moderation.rules.Hate |
"off", "alert", "hold", "block" |
"alert" |
Action for Hate findings. |
moderation.rules.IllegalServices |
"off", "alert", "hold", "block" |
"alert" |
Action for IllegalServices findings. |
moderation.rules.Minors |
"off", "alert", "hold", "block" |
"alert" |
Action for Minors findings. |
moderation.rules.NonConsent |
"off", "alert", "hold", "block" |
"alert" |
Action for NonConsent findings. |
moderation.rules.Political |
"off", "alert", "hold", "block" |
"alert" |
Action for Political findings. |
moderation.rules.SelfHarm |
"off", "alert", "hold", "block" |
"alert" |
Action for SelfHarm findings. |
moderation.rules.Trafficking |
"off", "alert", "hold", "block" |
"alert" |
Action for Trafficking findings. |
moderation.rules.Violence |
"off", "alert", "hold", "block" |
"alert" |
Action for Violence findings. |
moderation.rules.Weapons |
"off", "alert", "hold", "block" |
"alert" |
Action for Weapons findings. |
moderation.text |
boolean | true |
Moderate text messages. |
moderation.videos |
boolean | true |
Moderate videos. |
moderation.voice |
boolean | true |
Transcribe and moderate voice notes. |
notifications
Section titled “notifications”Notifications.
In-frame alerts while the chat is open. mondochat never emails your users; see webhooks.
| Key | Type | Default | Meaning |
|---|---|---|---|
notifications.banner |
boolean | true |
Show a banner on new messages. |
notifications.banner_throttle |
boolean | true |
At most one banner per 5s; off replaces it on every message (the sound stays throttled). |
notifications.sound |
boolean | true |
Play a sound on new messages. |
notifications.toast_opens |
"conversation", "panel" |
"conversation" |
Clicking the launcher toast opens its conversation, or only the panel. |
onboarding
Section titled “onboarding”Onboarding slides.
| Key | Type | Default | Meaning |
|---|---|---|---|
onboarding.enabled |
boolean | false |
Show onboarding slides. |
onboarding.repeat_hours |
integer, at least 1 or null | null |
Due again this many hours after it was last shown; null shows it once. |
onboarding.reshow_hours |
integer, at least 0 | 24 |
Show again after this many hours. |
onboarding.slides |
map of role key to object | {} |
Slides per role. |
onboarding.slides.<role>[].art |
"composer", "call_button", "incoming_call", "mail" or null |
null |
Built-in drawing above the title, in the theme colours. |
onboarding.slides.<role>[].body |
map of "it", "en", "es", "fr" to string |
{} |
Body per language. |
onboarding.slides.<role>[].bullets |
list of objects | [] |
Icon bullets. |
onboarding.slides.<role>[].bullets[].body |
map of "it", "en", "es", "fr" to string |
{} |
Body per language. |
onboarding.slides.<role>[].bullets[].icon |
"card", "shield", "mail", "lock", "coin" |
required | Bullet icon. |
onboarding.slides.<role>[].bullets[].signup |
map of "it", "en", "es", "fr" to string |
{} |
Link text per language that opens guests.nudge.signup_url. |
onboarding.slides.<role>[].bullets[].title |
map of "it", "en", "es", "fr" to string |
{} |
Title per language. |
onboarding.slides.<role>[].image_url |
https URL or null | null |
Slide image. |
onboarding.slides.<role>[].quote |
map of "it", "en", "es", "fr" to string |
{} |
Highlighted note per language. |
onboarding.slides.<role>[].title |
map of "it", "en", "es", "fr" to string |
{} |
Title per language. |
policy
Section titled “policy”One row per ordered role pair; a missing pair allows nothing.
Rows are directional: from is the acting user’s role, to the other user’s. A perms claim in the client token overrides any policy flag (for every to role) and any limit for that one user, for example {"call": false, "photos_per_day": 5}.
| Key | Type | Default | Meaning |
|---|---|---|---|
policy |
list of objects | [{"block":true,"buy":false,"call":false,"from":"member","message":true,"offers":false,"photo_consent_required":false,"photos":true,"require_vouch":false,"see_presence":true,"start_conversation":true,"to":"member"}] |
One row per ordered role pair; a missing pair allows nothing. |
policy[].block |
boolean | true |
May block the peer. |
policy[].buy |
boolean | false |
May buy from the peer. |
policy[].call |
boolean | false |
May start calls. |
policy[].from |
role key | required | Acting role. |
policy[].message |
boolean | true |
May send messages. |
policy[].offers |
boolean | false |
May send ad hoc offer cards. |
policy[].photo_consent_required |
boolean | false |
The receiver must accept photos per conversation. |
policy[].photos |
boolean | true |
May send photos. |
policy[].require_vouch |
boolean | false |
Starting needs a vouch token or the session’s peer. |
policy[].see_presence |
boolean | true |
May see the peer’s presence. |
policy[].start_conversation |
boolean | true |
May start a conversation. |
policy[].to |
role key | required | Target role. |
presence
Section titled “presence”Online status.
| Key | Type | Default | Meaning |
|---|---|---|---|
presence.enabled |
boolean | true |
Presence enabled. |
presence.fanout |
boolean | true |
Also push presence changes to each peer’s own channel. Turn off once every client subscribes with presence_pass. |
presence.in_inbox |
boolean | true |
Show the online dot and last seen in inbox rows. |
presence.show_last_seen |
boolean | true |
Show “last seen” times. |
undefined
Lets people install the chat as an app on their phone and get notifications, without an app store. Turn on enabled only after you fill in the name and a 512 px icon, because it is refused without them.
A person signs in once from your own page, through login_url, and the device then stays signed in for 30 days of use. Pushes show “New message” unless push_preview is on, and only go to people who are offline and have not read the message. Off keeps devices and refuses them until it is turned on again.
| Key | Type | Default | Meaning |
|---|---|---|---|
pwa.enabled |
boolean | false |
The chat as an installable app: device sign-in, manifest and pushes; off keeps devices but refuses them. |
pwa.icon_url |
https URL or null | null |
Square PNG of at least 512 px, edge to edge with the art inside the centre 80% circle (Android crops it to its shape); required when enabled. |
pwa.login_url |
https URL or null | null |
Page of the client that signs the user in and redirects to a fresh app link; placeholder {lang}. |
pwa.name |
string, up to 45 chars or null | null |
The name under the icon, 1 to 45 characters; required when enabled. |
pwa.push_preview |
boolean | false |
Notifications show the message preview instead of “New message”. |
rate_limits
Section titled “rate_limits”Rate limits.
| Key | Type | Default | Meaning |
|---|---|---|---|
rate_limits.app_requests_per_second |
integer, 1 to 10000 | 50 |
User requests per second per app. |
retention
Section titled “retention”Data retention.
Deletions run daily. null keeps data until the user is deleted through the server API.
| Key | Type | Default | Meaning |
|---|---|---|---|
retention.ip_log_days |
integer, at least 1 | 90 |
Delete IP logs after. |
retention.messages_days |
integer, at least 1 or null | null |
Delete messages after; null keeps. |
retention.photos_days |
integer, at least 1 or null | null |
Delete photos after; null keeps. |
User roles.
| Key | Type | Default | Meaning |
|---|---|---|---|
roles |
list of objects | [{"key":"member","label":{"en":"Member"},"seller":false}] |
User roles. |
roles[].key |
role key | required | Role key, the value of the role claim. |
roles[].label |
map of "it", "en", "es", "fr" to string |
{} |
Display name per language. |
roles[].seller |
boolean | false |
Users with this role sell services. |
safety
Section titled “safety”Blocking and reporting.
| Key | Type | Default | Meaning |
|---|---|---|---|
safety.ban_close_button |
boolean | true |
The banned panel has its own Close button. |
safety.report_reasons |
boolean | true |
A report asks for a reason; off is a plain confirm. |
webhooks
Section titled “webhooks”Webhook event options.
See https://docs.mondochat.io/webhooks/overview/. To notify users while the chat is closed, subscribe an endpoint to message.created, conversation.unread and call.ringing and send your own mail, push or SMS. Unread timers run only while an enabled endpoint subscribes to conversation.unread.
| Key | Type | Default | Meaning |
|---|---|---|---|
webhooks.message_text |
boolean | false |
message.created carries the text. |
webhooks.notify_kinds |
list of "text", "photo", "video", "voice" |
["text","photo","video","voice"] |
Message kinds that make a first contact or an unread streak. |
webhooks.unread_after_minutes |
list of integer, 1 to 10080 | [3] |
Send conversation.unread at each delay while the recipient still has unread messages; [] never. |
webhooks.unread_rearm |
boolean | false |
After the last delay has fired, the next message opens a new streak, so a recipient who never reads is notified again. |
workspace
Section titled “workspace”Organising conversations.
| Key | Type | Default | Meaning |
|---|---|---|---|
workspace.aliases |
boolean | false |
Users may give peers a private name. |
workspace.away_reply |
boolean | false |
Sellers may set a reply sent for them, once a day per chat, while away. |
workspace.inbound |
boolean | false |
Members may take messages from members only. |
workspace.notes |
boolean | false |
Users may keep a private note on a peer. |
workspace.pins |
boolean | false |
Users may pin up to 10 conversations. |
workspace.queue |
boolean | false |
Inbox views, archive, search, spent and purchases, and the sign-up invite. |
workspace.quick_replies |
boolean | false |
Sellers keep up to 20 saved replies and send one with a tap. |
workspace.reply_time |
boolean | false |
Conversations with a seller carry her typical reply time. |
workspace.tiers |
list of objects | [] |
Client tiers, lowest first; users get one by key. |
workspace.tiers[].color |
color #rrggbb |
required | Badge color. |
workspace.tiers[].key |
role key | required | Tier key, the value of a user’s tier. |
workspace.tiers[].label |
map of "it", "en", "es", "fr" to string |
{} |
Display name per language. |