Update community theme colors
Change your community's theme, logos, loading animation or call-to-action button, sending only the parts you want to change.
- Method
- PUT
- Path
https://api.returning.ai / v1/ appearance - Permission
- appearance
- Retries
- Safe to repeat; the same values give the same result
When to use this
- You switch the community to one of the built-in themes, or to a custom theme your team made in the dashboard.
- You update the community icon and logos after a rebrand.
- You turn the call-to-action button on or off, or change its text and link, for a campaign.
Authentication#
- Header
Authorization: Bearer <API_KEY>- Permission
- appearanceShown in the dashboard as “Appearance”
Use a Community API key with appearance, and keep it on your server. The key decides the community, so never send a community ID. The published path lacks /v1: call PUT /v1/appearance, because PUT /appearance returns 404.
Behaviour#
Only the fields you send change; everything else keeps its value, down to single colours and single call-to-action settings. The change shows for every member straight away. Members aren't notified.
A theme is picked by name. For a built-in theme, the colours come from that theme and any palette you send is ignored. For a custom theme made in your dashboard, its saved colours are used and the colours you send in palette replace those. A name that is neither returns 400. The built-in dark themes are black-beauty, dark-knight, botanical-forest, rustic-pottery and hearts-desire. The built-in light themes are aqua-lolly, lush-blush, white-smoke, prairie-dance, farsighted and violet-dream.
Images must be https or http URLs, or paths starting with /assets/files/, and must end in .png, .jpg, .jpeg, .gif, .webp or .svg. Send null to remove an image.
Request#
Headers#
Body#
Send a JSON object with one or more of theme, branding, loadingAnimation and callToAction. Any field not listed here is rejected with 400.
Colours are hex (#6755ff, also 3, 4 or 8 digits), rgb() or rgba() values, or one of var(--accent-1), var(--text-1) and var(--divider-lines-1). Named colours such as red are rejected.
For a loading animation, send mode: "preset" with preset, or mode: "custom" with activeTheme and a custom entry for that theme. Each custom entry sets type and fills only the matching html or lottie field.
Eg{"defaultMode": "dark", "dark": {"name": "dark-knight"}}
RuleLato, Roboto, Noto, Open Sans, Source Sans Pro, Inter, Montserrat, Poppins, Nunito, Prata, Bebas Neue, Monospace, Courier, Monaco, Quicksand, Fira Code, Fira Sans, Rubik, Noto Sans, Lora, Playfair Display
Rulelight, dark
Eg"dark"
Eg{"name": "dark-knight"}
dark-knight, or the name of a custom theme made in your dashboard. Required when you send palette.RuleTheme name
Eg"dark-knight"
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
linear-gradient(90deg, #596cff 0%, #8f5cff 100%).RuleAny non-empty CSS value
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
text1. Worked out from text1 when you leave it out.RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
0 8px 24px rgba(0, 0, 0, 0.24).RuleAny non-empty CSS value
RuleHex or rgb() colour
label. Send label instead.RuleHex or rgb() colour; must equal label when both are sent
RuleHex or rgb() colour
RuleHex or rgb() colour
white-smoke, or the name of a custom theme made in your dashboard. Required when you send palette.RuleTheme name
Eg"white-smoke"
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
linear-gradient(90deg, #596cff 0%, #8f5cff 100%).RuleAny non-empty CSS value
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
text1. Worked out from text1 when you leave it out.RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
RuleHex or rgb() colour
0 8px 24px rgba(0, 0, 0, 0.24).RuleAny non-empty CSS value
RuleHex or rgb() colour
label. Send label instead.RuleHex or rgb() colour; must equal label when both are sent
RuleHex or rgb() colour
RuleHex or rgb() colour
Eg{ ... }
null to remove it.RuleImage URL or path; nullable
Eg{ ... }
null to remove it.RuleImage URL or path; nullable
Eg"https://cdn.example.com/brand/logo-dark.svg"
null to remove it.RuleImage URL or path; nullable
Eg"https://cdn.example.com/brand/logo-light.svg"
preset for a built-in animation, or custom for your own.Rulepreset, custom
mode is preset.Rulebar, default, circle, square
mode is custom.Ruledark, light
html or lottie. Send the matching field and leave the other empty.Rulehtml, lottie
iframe and embed tags, and javascript: or data: URLs are rejected..json.html or lottie. Send the matching field and leave the other empty.Rulehtml, lottie
iframe and embed tags, and javascript: or data: URLs are rejected..json.Eg{ ... }
Egtrue
Eg{ ... }
isEnabled as true.Eg"Open an account"
https or http URL. An empty string removes the link.RuleURL or empty, max 2048 chars
Eg"https://www.example.com/open-account"
new tab or the same one.Rulenew, same
Eg"new"
null to remove it.RuleImage URL or path; nullable
Rulesquare, rounded, circle
RuleHex or rgb() colour
Rule0px to 20px
RuleHex or rgb() colour
RuleLato, Roboto, Noto, Open Sans, Source Sans Pro, Inter, Montserrat, Poppins, Nunito, Prata, Bebas Neue, Monospace, Courier, Monaco, Quicksand, Fira Code, Fira Sans, Rubik, Noto Sans, Lora, Playfair Display
Rule1px to 20px
RuleHex or rgb() colour
Rulenormal, italic, bold
Ruleleft, center, right
curl --request PUT \
--url https://api.returning.ai/v1/appearance \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
"theme": {
"defaultMode": "dark",
"dark": {
"name": "dark-knight"
}
},
"branding": {
"logo": {
"dark": "https://cdn.example.com/brand/logo-dark.svg",
"light": "https://cdn.example.com/brand/logo-light.svg"
}
},
"callToAction": {
"isEnabled": true,
"content": {
"text": "Open an account",
"link": "https://www.example.com/open-account",
"linkBehavior": "new"
}
}
}'
Response#
A 200 returns the community's whole appearance after the change in data, including the parts you didn't send. Palette colours come back under label, never mentionBackground.
Eg{"status": "success", "statusCode": 200}
Rulesuccess
Rule200
Eg"Update community appearance success."
Eg{ ... }
communityID.Eg"66f000000000000000000010"
Eg"66f000000000000000000010"
Eg{ ... }
Eg"Inter"
dark or light.Eg"dark"
Eg{ ... }
Eg"dark-knight"
Eg{ ... }
Eg"rgb(224, 208, 162)"
Eg"rgb(239, 225, 186)"
Eg"rgb(250, 240, 212)"
Eg"rgb(254, 249, 236)"
Eg"rgb(252, 252, 252)"
Eg"rgb(210, 217, 224)"
Eg"rgb(200, 210, 220)"
Eg"rgb(158, 171, 185)"
Eg"rgb(151, 165, 181)"
Eg"rgb(133, 147, 164)"
Eg"rgb(6, 9, 33)"
Eg"rgb(13, 17, 42)"
Eg"rgb(23, 26, 54)"
Eg"rgb(23, 27, 56)"
Eg"rgb(31, 35, 65)"
Eg"rgb(37, 41, 73)"
Eg"rgb(53, 59, 92)"
Eg"rgb(79, 92, 108)"
Eg"rgb(158, 171, 185)"
0 8px 24px rgba(0, 0, 0, 0.24).Eg"rgba(6, 9, 33, 0.5)"
Eg"rgb(34, 34, 34)"
Eg"rgb(59, 59, 59)"
linear-gradient(90deg, #596cff 0%, #8f5cff 100%).text1. Worked out from text1 when you leave it out.Eg{ ... }
Eg"white-smoke"
Eg{ ... }
Eg"rgb(48, 65, 97)"
Eg"rgb(64, 87, 130)"
Eg"rgb(159, 171, 192)"
Eg"rgb(193, 202, 218)"
Eg"rgb(27, 28, 29)"
Eg"rgb(71, 71, 71)"
Eg"rgb(84, 84, 84)"
Eg"rgb(103, 103, 103)"
Eg"rgb(119, 119, 119)"
Eg"rgb(146, 146, 146)"
Eg"rgb(252, 252, 252)"
Eg"rgb(240, 240, 240)"
Eg"rgb(233, 233, 233)"
Eg"rgb(228, 228, 228)"
Eg"rgb(211, 211, 211)"
Eg"rgb(199, 199, 199)"
Eg"rgb(167, 167, 167)"
Eg"rgb(146, 146, 146)"
Eg"rgb(103, 103, 103)"
0 8px 24px rgba(0, 0, 0, 0.24).Eg"rgba(27, 28, 29, 0.35)"
Eg"rgb(203, 203, 203)"
Eg"rgb(234, 234, 234)"
linear-gradient(90deg, #596cff 0%, #8f5cff 100%).text1. Worked out from text1 when you leave it out.Eg{ ... }
null.RuleNullable
Eg"https://cdn.example.com/brand/icon.png"
Eg{ ... }
null.RuleNullable
Eg"https://cdn.example.com/brand/logo-dark.svg"
null.RuleNullable
Eg"https://cdn.example.com/brand/logo-light.svg"
Eg{"mode": "preset", "preset": "default"}
preset or custom.Eg"preset"
Eg"default"
html or lottie.html or lottie.Eg{ ... }
true when the button is shown.Egtrue
true when the button is shown on mobile.Egtrue
Eg{ ... }
Eg"Open an account"
Eg"https://www.example.com/open-account"
new or same.Eg"new"
null.RuleNullable
Egnull
Eg{ ... }
Eg"rounded"
Eg"var(--accent-1)"
Eg"0px"
Eg"var(--accent-1)"
Eg{ ... }
Eg"Inter"
Eg"14px"
Eg"var(--text-1)"
Eg"bold"
Eg"center"
Eg"2026-09-20T08:30:00.000Z"
Eg"2026-09-26T08:30:00.000Z"
{
"meta": {
"status": "success",
"statusCode": 200
},
"message": "Update community appearance success.",
"data": {
"_id": "66f000000000000000000010",
"communityID": "66f000000000000000000010",
"theme": {
"font": "Inter",
"defaultMode": "dark",
"dark": {
"name": "dark-knight",
"palette": {
"accent1": "rgb(224, 208, 162)",
"accent2": "rgb(239, 225, 186)",
"accent3": "rgb(250, 240, 212)",
"accent4": "rgb(254, 249, 236)",
"text1": "rgb(252, 252, 252)",
"text2": "rgb(210, 217, 224)",
"text3": "rgb(200, 210, 220)",
"text4": "rgb(158, 171, 185)",
"text5": "rgb(151, 165, 181)",
"text6": "rgb(133, 147, 164)",
"text7": "rgb(27, 28, 29)",
"background1": "rgb(6, 9, 33)",
"background2": "rgb(13, 17, 42)",
"background3": "rgb(23, 26, 54)",
"background4": "rgb(23, 27, 56)",
"background5": "rgb(31, 35, 65)",
"background6": "rgb(37, 41, 73)",
"background7": "rgb(53, 59, 92)",
"divider1": "rgb(79, 92, 108)",
"divider2": "rgb(158, 171, 185)",
"shadow": "rgba(6, 9, 33, 0.5)",
"label": "rgb(23, 26, 54)",
"scrollThumb": "rgb(34, 34, 34)",
"scrollTrack": "rgb(59, 59, 59)"
},
"colorSettings": {
"accent1": {
"type": "solid",
"solid": "rgb(224, 208, 162)"
},
"accent2": {
"type": "solid",
"solid": "rgb(239, 225, 186)"
},
"accent3": {
"type": "solid",
"solid": "rgb(250, 240, 212)"
},
"accent4": {
"type": "solid",
"solid": "rgb(254, 249, 236)"
},
"text1": {
"type": "solid",
"solid": "rgb(252, 252, 252)"
},
"text2": {
"type": "solid",
"solid": "rgb(210, 217, 224)"
},
"text3": {
"type": "solid",
"solid": "rgb(200, 210, 220)"
},
"text4": {
"type": "solid",
"solid": "rgb(158, 171, 185)"
},
"text5": {
"type": "solid",
"solid": "rgb(151, 165, 181)"
},
"text6": {
"type": "solid",
"solid": "rgb(133, 147, 164)"
},
"text7": {
"type": "solid",
"solid": "rgb(27, 28, 29)"
},
"background1": {
"type": "solid",
"solid": "rgb(6, 9, 33)"
},
"background2": {
"type": "solid",
"solid": "rgb(13, 17, 42)"
},
"background3": {
"type": "solid",
"solid": "rgb(23, 26, 54)"
},
"background4": {
"type": "solid",
"solid": "rgb(23, 27, 56)"
},
"background5": {
"type": "solid",
"solid": "rgb(31, 35, 65)"
},
"background6": {
"type": "solid",
"solid": "rgb(37, 41, 73)"
},
"background7": {
"type": "solid",
"solid": "rgb(53, 59, 92)"
},
"divider1": {
"type": "solid",
"solid": "rgb(79, 92, 108)"
},
"divider2": {
"type": "solid",
"solid": "rgb(158, 171, 185)"
},
"shadow": {
"type": "solid",
"solid": "rgba(6, 9, 33, 0.5)"
},
"label": {
"type": "solid",
"solid": "rgb(23, 26, 54)"
},
"scrollThumb": {
"type": "solid",
"solid": "rgb(34, 34, 34)"
},
"scrollTrack": {
"type": "solid",
"solid": "rgb(59, 59, 59)"
}
}
},
"light": {
"name": "white-smoke",
"palette": {
"accent1": "rgb(48, 65, 97)",
"accent2": "rgb(64, 87, 130)",
"accent3": "rgb(159, 171, 192)",
"accent4": "rgb(193, 202, 218)",
"text1": "rgb(27, 28, 29)",
"text2": "rgb(71, 71, 71)",
"text3": "rgb(84, 84, 84)",
"text4": "rgb(103, 103, 103)",
"text5": "rgb(119, 119, 119)",
"text6": "rgb(146, 146, 146)",
"text7": "rgb(252, 252, 252)",
"background1": "rgb(252, 252, 252)",
"background2": "rgb(240, 240, 240)",
"background3": "rgb(233, 233, 233)",
"background4": "rgb(228, 228, 228)",
"background5": "rgb(211, 211, 211)",
"background6": "rgb(199, 199, 199)",
"background7": "rgb(167, 167, 167)",
"divider1": "rgb(146, 146, 146)",
"divider2": "rgb(103, 103, 103)",
"shadow": "rgba(27, 28, 29, 0.35)",
"label": "rgb(233, 233, 233)",
"scrollThumb": "rgb(203, 203, 203)",
"scrollTrack": "rgb(234, 234, 234)"
},
"colorSettings": {
"accent1": {
"type": "solid",
"solid": "rgb(48, 65, 97)"
},
"accent2": {
"type": "solid",
"solid": "rgb(64, 87, 130)"
},
"accent3": {
"type": "solid",
"solid": "rgb(159, 171, 192)"
},
"accent4": {
"type": "solid",
"solid": "rgb(193, 202, 218)"
},
"text1": {
"type": "solid",
"solid": "rgb(27, 28, 29)"
},
"text2": {
"type": "solid",
"solid": "rgb(71, 71, 71)"
},
"text3": {
"type": "solid",
"solid": "rgb(84, 84, 84)"
},
"text4": {
"type": "solid",
"solid": "rgb(103, 103, 103)"
},
"text5": {
"type": "solid",
"solid": "rgb(119, 119, 119)"
},
"text6": {
"type": "solid",
"solid": "rgb(146, 146, 146)"
},
"text7": {
"type": "solid",
"solid": "rgb(252, 252, 252)"
},
"background1": {
"type": "solid",
"solid": "rgb(252, 252, 252)"
},
"background2": {
"type": "solid",
"solid": "rgb(240, 240, 240)"
},
"background3": {
"type": "solid",
"solid": "rgb(233, 233, 233)"
},
"background4": {
"type": "solid",
"solid": "rgb(228, 228, 228)"
},
"background5": {
"type": "solid",
"solid": "rgb(211, 211, 211)"
},
"background6": {
"type": "solid",
"solid": "rgb(199, 199, 199)"
},
"background7": {
"type": "solid",
"solid": "rgb(167, 167, 167)"
},
"divider1": {
"type": "solid",
"solid": "rgb(146, 146, 146)"
},
"divider2": {
"type": "solid",
"solid": "rgb(103, 103, 103)"
},
"shadow": {
"type": "solid",
"solid": "rgba(27, 28, 29, 0.35)"
},
"label": {
"type": "solid",
"solid": "rgb(233, 233, 233)"
},
"scrollThumb": {
"type": "solid",
"solid": "rgb(203, 203, 203)"
},
"scrollTrack": {
"type": "solid",
"solid": "rgb(234, 234, 234)"
}
}
}
},
"branding": {
"icon": "https://cdn.example.com/brand/icon.png" ,
"logo": {
"dark": "https://cdn.example.com/brand/logo-dark.svg" ,
"light": "https://cdn.example.com/brand/logo-light.svg"
}
},
"loadingAnimation": {
"mode": "preset",
"preset": "default"
},
"callToAction": {
"isEnabled": true,
"isMobileEnabled": true,
"content": {
"text": "Open an account",
"link": "https://www.example.com/open-account" ,
"linkBehavior": "new",
"iconImage": null
},
"style": {
"shape": "rounded",
"backgroundColor": "var(--accent-1)",
"borderSize": "0px",
"borderColor": "var(--accent-1)"
},
"typography": {
"fontFamily": "Inter",
"fontSize": "14px",
"fontColor": "var(--text-1)",
"fontStyle": "bold",
"textAlign": "center"
}
},
"createdAt": "2026-09-20T08:30:00.000Z",
"updatedAt": "2026-09-26T08:30:00.000Z"
}
}
Errors#
Every error carries meta.status and meta.statusCode, with the reason in detail. Key errors and field validation errors also carry meta.code; other 400 errors and the 500 and 502 errors don't.
Fix the request05
detail says which: Request body must be a non-empty object (send at least one of theme, branding, loadingAnimation or callToAction), Theme 'name' not found for mode 'dark' (use a built-in theme name or a custom theme from your dashboard), or the unsafe loading-animation HTML message (remove scripts, event handlers and embedded frames). These 400 errors have no meta.code. Nothing changed.VALIDATION_FAILEDFix the requestrgb(), a palette without name, a loading animation whose mode doesn't match its other fields, or a field this endpoint doesn't accept. detail names each field. Nothing changed.AUTH_API_KEY_REQUIREDFix the requestAuthorization: Bearer <API_KEY>.AUTH_API_KEY_INVALIDFix the requestAUTH_PERMISSION_REQUIREDFix the requestappearance. Add the permission in Settings > Integration > API Keys.Fix the data01
Retry with backoff03
Upstream service is temporarily unavailable.). Retry with backoff.AUTH_API_KEY_VALIDATION_FAILEDRetry with backoff{
"meta": {
"status": "error",
"statusCode": 400,
"code": "VALIDATION_FAILED"
},
"message": "Validation failed",
"detail": {
"theme": "theme has invalid name, palette, or label alias relationships"
},
"solution": "Please check the request and try again"
}