The Notiondesk API lets you read and change your help center's branding: colors, fonts, layout, header and footer, links, search and feedback display, and theme. Use it to roll out the same brand across several help centers, or to keep your branding in code next to the rest of your design system.
| Method | Path | Scope | Does |
|---|---|---|---|
GET | /sites/{site_id}/settings/customize | read | Reads the branding |
PATCH | /sites/{site_id}/settings/customize | admin | Updates the branding |
GET | /sites/{site_id}/themes | read | Lists the available themes |
Branding is visible on every public page, so updating it needs the admin scope, not write. Only a key created by a workspace owner or Admin holds admin.
To change branding in the dashboard instead, see Customize your help center colors and fonts.
What the API does not change
These stay dashboard-only. The read route does not return them, and an update that names one answers 400 validation_error:
- custom CSS and custom JavaScript. See Customize your help center with custom CSS;
- logos (light and dark), footer logos (light and dark), favicon, social media image and header images (light and dark).
Read your branding
curl "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/settings/customize" \
-H "Authorization: Bearer $NOTIONDESK_API_KEY" \
-H "nd-version: 2026-10-16"The response holds exactly the fields the update accepts, under the same names. A shortened example:
{
"colors": {
"primary": "#2563eb",
"text": "#111827",
"link": "#2563eb",
"link_hover": "#1d4ed8",
"header": "#2563eb",
"header_text": "#ffffff",
"background": "#ffffff",
"footer_background": ""
},
"appearance": null,
"layout_collection": "column_2",
"layout_article": "column_1",
"footer_layout": "compact",
"primary_font": "Inter",
"header_links": [{"title": "Docs", "url": "https://example.com/docs"}],
"footer_links": [{"title": "Product", "links": [{"title": "Pricing", "url": "https://example.com/pricing"}]}],
"show_powered_by_badge": false,
"theme": null
}- A color that was never set reads
"".colors.darkholds the dark mode colors, and is absent when the help center has no dark palette.
- A list that holds nothing reads
[].
themeis thecodeof the current theme, ornull.
Update your branding
curl -i -X PATCH "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/settings/customize" \
-H "Authorization: Bearer $NOTIONDESK_API_KEY" \
-H "nd-version: 2026-10-16" \
-H "Content-Type: application/json" \
-d '{"primary_font": "Inter", "colors": {"primary": "#2563eb"}, "header_links": [{"title": "Docs", "url": "https://example.com/docs"}]}'The route answers 204 No Content. Read the branding again to confirm the change.
- A field you leave out keeps its value. Inside
colors, a color you leave out keeps its value too.
- A list you send replaces the whole stored list, and
[]empties it.
- Colors are
#rrggbbhexadecimal values, or"". Short forms such as#fffare rejected.
- Send JSON booleans. The values
0and"false"are read astrue, andnullasfalse.
Safe updates: read, change, send back
The safest way to update branding is to send back what you read, with your changes applied. A field sent with its current value is accepted on any plan, so this pattern never trips a plan limit on fields you did not touch.
const url = "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/settings/customize";
const headers = {
Authorization: `Bearer ${process.env.NOTIONDESK_API_KEY}`,
"nd-version": "2026-10-16",
"Content-Type": "application/json",
};
const branding = await (await fetch(url, { headers })).json();
branding.colors.primary = "#7c3aed";
branding.colors.link = "#7c3aed";
const response = await fetch(url, { method: "PATCH", headers, body: JSON.stringify(branding) });
console.log(response.status);A body sent back can still be refused in two cases. Fix the value in the dashboard, or leave that field out of the body:
- a stored color in a short form such as
#fff;
- a stored list longer than the limits below.
Fields you can change
| Area | Fields |
|---|---|
| Colors | colors with primary, text, link, link_hover, header, header_top, header_text, header_top_text, header_link, header_link_hover, background, footer_background, footer_text, article_card_background, collection_card_background, aside_menu_text, aside_menu_text_hover, aside_menu_background_hover, and dark with the same fields for dark mode |
| Appearance | appearance (default: light, dark or system; strict: boolean) |
| Fonts and layout | primary_font, secondary_font, layout_collection, layout_article (column_1, column_2 or column_3), theme |
| Header | header_variant (hero or bar), headline_size, header_background_type (color, image or gradient), header_gradient_type, header_gradient_angle, header_gradient_colors, header_shape_type, header_links, header_link_translations, homepage_link, homepage_link_translations |
| Search | search_bar_length, search_bar_alignment, search_bar_corner_style, search_bar_shadow, search_result_variant, instant_search, segmented_search_results, ai_search_enabled, ai_search_display_mode, ai_search_aside_position |
| Collections | collections_style, collection_card_corner_style, collection_section_variant, collection_content_variant, collapsable_sub_categories, collapsable_sub_categories_default_state, show_collection_icons, show_collection_descriptions, show_collection_content, show_collection_authors, show_collection_articles_count |
| Articles | articles_style, article_card_corner_style, content_tabs_style, article_footer_variant, table_of_contents_position, show_table_of_contents, show_article_icons, show_article_descriptions, show_article_authors, show_related_articles, show_breadcrumb_icon, show_article_publish_date, show_article_page_author |
| Feedback | show_article_feedback_form, require_email_for_article_feedback, feedback_reaction_style (emoji or thumbs), feedback_question_label, feedback_comment_mode |
| Side menu | aside_menu_expansion_mode, show_aside_menu_article_icons, show_aside_menu_collection_icons |
| Footer | footer_layout (compact, standard or multi_level), footer_shape_type, footer_links, footer_link_translations, footer_text_translations, social_media_links, social_link_translations |
| Contact button | show_contact_us_button, contact_us_custom_link, contact_us_button_position (header or footer) |
| Badge | show_powered_by_badge |
Links are objects with a title and a url. Footer link groups are objects with a title and a links list. Translations are objects with a locale and the translated links, groups or value.
Plan limits
| Fields | Plan feature needed to change them |
|---|---|
show_powered_by_badge | Remove branding. See How to remove the Powered by Notiondesk badge |
theme, header_shape_type, instant_search, segmented_search_results, ai_search_display_mode, ai_search_aside_position, show_article_publish_date, show_article_page_author, the feedback fields, the side menu fields, the footer fields, the social links and the contact button fields | Advanced customization |
Changing one of them on a plan without the feature answers 403 permission_denied, the message names the feature, and nothing is written.
Without advanced customization, the footer colors keep their stored value with no error. Without an enabled AI chatbot, ai_search_display_mode, ai_search_aside_position and ai_search_enabled: true also keep their stored value.
List limits
- Each list holds up to 100 entries, and so does each
linksorgroupslist inside it.
- All lists together hold up to 3,000 entries, counting nested entries and the stored lists your body leaves out.
The contact button turns the contact form on
While show_contact_us_button is true on a plan with advanced customization, every branding update turns the help center's contact form on, as a save in the dashboard does. See the contact form section of Update help center settings with the Notiondesk API.
Choose a theme
curl "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/themes" \
-H "Authorization: Bearer $NOTIONDESK_API_KEY" \
-H "nd-version: 2026-10-16"{
"object": "list",
"data": [
{
"code": "ocean",
"name": "Ocean",
"description": "A calm, airy theme",
"preview_image_url": "https://example.com/ocean.png",
"layout": "default"
}
],
"has_more": false,
"next_cursor": null
}The list always holds every theme in one response, in display order. Apply one by sending its code as theme:
curl -i -X PATCH "https://api.notiondesk.so/sites/c74781f6-1aea-4e17-ab23-bbb8b4d174b6/settings/customize" \
-H "Authorization: Bearer $NOTIONDESK_API_KEY" \
-H "nd-version: 2026-10-16" \
-H "Content-Type: application/json" \
-d '{"theme": "ocean"}'Setting a theme needs advanced customization. Sending "theme": null leaves the theme unchanged.
Errors
| Status | code | When |
|---|---|---|
| 400 | validation_error | The body names a dashboard-only field, a value is not one of the field's choices, a color is not #rrggbb, a required value is null, a list is too long, theme names no theme, or the body has an unknown field |
| 403 | insufficient_scope | The key lacks the read scope, or admin for the update |
| 403 | permission_denied | The update changes a field your plan does not include, or the key's creator cannot change this help center |
| 404 | not_found | The help center does not exist in your workspace |
Field errors name the value, for example colors.primary or footer_links[0].links. See Handle Notiondesk API errors, rate limits, and retries.
Common questions
Can I upload a logo with the Notiondesk API?
No. Logos, the favicon and header images are set in the dashboard.
Can I change custom CSS or JavaScript with the API?
No. Custom CSS and custom JavaScript are dashboard-only, and the API never reads or changes them.
Why does my update answer 403 on a field I did not change?
You sent a value that differs from the stored one on a field your plan does not include. Read the branding first and send the stored value back, or leave the field out.
Does a branding update need a sync?
No. The update refreshes the whole help center by itself.