Change a help center's design from code: its theme, pages and components, navigation, custom CSS and scripts. You edit the same draft that the template editor opens, a person reviews it there with a live preview, and visitors see nothing until you publish.
Start by reading where the design stands:
curl https://api.helpcenter.io/v1/template \
-H "apikey: $HELPCENTER_API_KEY" \
-H "Accept: application/json"
The response names the draft and the live version, gives the link to review the draft, and outlines the draft (trimmed here to the colors and the synced regions):
{
"status": "success",
"template": {
"engine": "v2",
"site": {
"id": 391,
"name": "Acme Help",
"subdomain": "acme"
},
"draft": {
"id": 223,
"etag": "\"223-1790778695000-9b93f595\"",
"updated_at": "2026-09-30T14:31:35+00:00",
"preset_key": "compass",
"has_unpublished_changes": false
},
"published": {
"id": 222,
"status": "published",
"live": true,
"message": "Initial template",
"preset_key": "compass",
"published_at": "2026-09-30T14:31:28+00:00"
},
"urls": {
"live_site": "https://acme.helpcenter.io",
"customizer": "https://helpcenter.io/app/sites/acme/customizer-v2",
"draft_preview": "https://acme.helpcenter.dev"
}
},
"outline": {
"theme": {
"colors": {
"primary": "#24644f",
"accent": "#edf6f1",
"fg": "#1c2926",
"bg": "#ffffff",
"muted": "#626c68",
"surface": "#f6f8f6",
"border": "#e2e8e4"
}
},
"syncedRegions": [
"footer"
],
"syncedButDiverged": null
}
}
How it works
Read.
GET /v1/templatereturns an outline of the draft and itsetag. Read any part in full withGET /v1/template/draft, and look up what you can change withGET /v1/template/schema.Start from a theme, if you like.
POST /v1/template/themes/{themeKey}/applycopies a theme onto the draft, whole or only some parts of it, such as its colors and fonts.Edit.
PATCH /v1/template/draftapplies a batch of operations to the draft, all or nothing.Review. Send the person responsible for the design to
template.urls.customizer. The template editor opens on the same draft, with a live preview, and they can adjust it there.Publish.
POST /v1/template/publishputs the draft live. It's the only call that changes what visitors see.
One draft per help center, shared with the template editor. Your edits appear in the editor, and edits made in the editor appear in your next read, so a person can pick up where your code stopped, and the other way round (see Get started with the template editor).
Visitors keep seeing the live design until you publish.
urls.draft_previewshows the draft as a website too, but only to signed-in members of the help center's team.You can always go back. Discard the draft to return to the live design, or load an earlier version into the draft and review it before it goes live again.
Help centers that use the template editor
The design API works with help centers designed in the template editor. On a help center that hasn't switched to the template editor yet, every endpoint answers 409 Conflict with the code template_migration_required, and customizer_url in the body links to its template editor. The request changes nothing.
Switching is the help center owner's decision, and it happens in the dashboard: the template editor turns the current design into a draft to review, and publishing that draft completes the switch. From then on, the design API works.
Get access
Access to the design is granted separately from access to your content, and checked twice on every request. The design API works on every plan, with an API key or an OAuth access token.
An API key must be created with design access. When you create the key, tick Allow access to the help center design under Design (see API keys). A Read only key then holds
design.readand can read the design. A Read & write key also holdsdesign.writeand can change and publish it. A key created without the option can't reach the design, whatever its scope, and you can't add the option to an existing key: create a new one. Keys with design access carry a Design tag in the list of keys.OAuth apps get
design.readanddesign.writeonly when they ask for these scopes and the person connecting the app approves them.content.readandcontent.writedon't include them (see Scopes).The person behind the credential must be able to edit the design. Every request checks that the team member who created the key, or who approved the app, can still edit this help center's design in the dashboard: an Owner, Admin or Editor. Translators and Viewers can't. Without that access, the API answers
403 Forbiddenwith the codedesign_forbidden, whatever the credential holds. Replace the key with one created by a current Owner or Admin, or connect the app again as someone who can edit the design.
A credential without the scope gets 403 with the code insufficient_scope and a message that names the scope, such as This action requires the design.write scope.
design.write reaches the help center's custom CSS and scripts, which run in every visitor's browser once the design is published. Treat a key with design access like an admin password: keep it on a server, give it only to tools you trust, and delete it when the work is done.
Endpoints
Method and path | What it does | Scope |
|---|---|---|
| Read where the design stands, and an outline of the draft |
|
| Read the draft document, whole or one part |
|
| Read the customization guide |
|
| List the themes |
|
| Read one theme, and what applying it would change |
|
| Apply a theme to the draft |
|
| Edit the draft |
|
| Discard the draft's changes |
|
| Publish the draft |
|
| List the published versions |
|
| Load an earlier version into the draft |
|
Every request counts toward your credential's 300 requests a minute, and the five writes also toward its 120 writes a minute (see Rate limits).
The template object
GET /v1/template and every write return template, which says where the design stands:
Field | Type | Description |
|---|---|---|
| string | Always |
| object | The help center: |
| integer | The draft. Every publish starts a new draft, with a new |
| string | The draft's current version. Send it with a write to change only the draft you read (see Avoid overwriting someone else's changes, below). The quotes inside it are part of the value. |
| string | When the draft last changed, ISO 8601 with an offset. |
| string | The key of the theme the draft came from, or of the theme last applied to it. Applying only some sections of a theme sets it too, and discarding the draft leaves it as it was. |
| boolean |
|
| object or null | The live version: |
| string | The live help center, on its custom domain when it has one. |
| string | The template editor, open on this draft. Send people here to review your changes and to publish them. |
| string | The draft as a website. It opens only for signed-in members of the help center's team. |
The design document
A help center's design is one document. GET /v1/template/draft returns it, and the customization guide describes every part of it.
Part | What it holds |
|---|---|
| The help center's name, tagline, logo, dark-mode logo, favicon and main website. When the draft is published, |
| Colors for light mode ( |
| The SEO title and description, social sharing texts and image, text direction and the search engine settings. |
| Six pages: |
| The regions every page shares (see Synced regions whose pages differ, below). |
|
|
| Labelled blocks of custom CSS. |
| Kept by HelpCenter.io. You can't set them. |
A component is {id, type, config, visibleOn, children}. Its id is how operations find it, and how custom CSS targets it, with [data-hc-instance-id="<id>"]. To see how pages, regions and shared headers look in the template editor, see Pages, regions and shared headers explained.
Text you set is in the help center's default language. Translations of design texts are made in the dashboard (see Translate texts in your design). The API reads them but can't edit them. When you move or copy a component, its translations go with it, and when you remove it, they go too.
Read the design
/v1/templateRead where the design stands: the draft, the live version, the review link, and an outline of the draft.
The outline holds what you need to decide on an edit, without the full document:
brand,themeandmeta, in full.pages: for each page,settingswith itslayoutand the settings that differ from the defaults, andregionswith each region's components. A component shows itsid,type, the settings that differ from its defaults, and itschildren. Values longer than 120 characters are cut short and large lists are summarized: read them whole withsection=component.sharedRegions: the synced regions whose pages are the same, listed once instead of on every page.syncedButDiverged:null, or an object whoseregionslists the synced regions whose pages differ, with anoteon how to choose. Those regions then appear under each page.styledByCustomCsson a region: the labels of the custom CSS blocks that restyle it. A theme's CSS can lay a region out its own way, and whatever you add there is laid out the same way.navigation,scriptsandcustomCss: lists of ids and labels, without the bodies.presetStartedFromis the theme the document came from.
If the help center has no draft yet, for example because no one has opened its design since the help center was created, reading starts a new one from the live design.
Read the draft document
/v1/template/draftRead the draft document, whole or one part of it.
Every answer has section, the draft's etag and the part you asked for in data. The whole document of a theme can be large (the Compass theme's is about 85 KB, most of it custom CSS), so read the part you need.
| Other parameters |
|
|---|---|---|
| None | The whole document. |
| None | That part of the document. |
| None | The menu items. |
|
| Every script, or the one with that |
|
| Every custom CSS block, or the one with that |
|
| One page: its settings and its six regions. |
|
| One region: |
|
|
|
| None | The translated design texts, by language code. An empty list, |
An unknown component id answers 404 Not Found with component_not_found, and an unknown script or CSS block id 404 with not_found.
Read the customization guide
/v1/template/schemaRead the customization guide: every operation, component, setting and rule the draft accepts, one topic at a time.
The guide is built from the components and settings this API accepts, so it always matches what PATCH /v1/template/draft checks. Read it before you change a component's settings.
| What it holds |
|---|---|
| The model of pages, regions and components, every operation with an example, an index of the components (what each is for, and on which pages and in which regions it can go), this help center's languages, FAQ sections and uploaded fonts, and the limits. |
| Every setting of the component types you list in |
| Every path the |
| The color tokens, font formats, radius and density, and the CSS variables they become. |
| How custom CSS is scoped, the hooks for components and regions, dark mode, and what is removed. |
| Script positions, kinds, page filters and safety. |
| The format of menu items. |
List themes
/v1/template/themesList the themes you can apply to the draft.
Each theme has a key and name, a tagline, summary and best_for, its light and dark palettes, fonts and density, a preview_image and, for some, an industry. current is true for the theme in the draft's preset_key. The blank theme is an empty canvas: default colors, and no components except an ArticleMetadata on the article page and a ContactForm on the contact page.
Read a theme
/v1/template/themes/{themeKey}Read one theme, and which sections of the draft applying it would change.
One theme adds its description, screenshots of the home, category, article and search pages and of dark mode and a phone, and anatomy, what each page is built from. changed_sections lists the sections of your draft that applying the theme would change, and sections every section you can apply.
Apply a theme
/v1/template/themes/{themeKey}/applyCopy a theme onto the draft: every section, or only the ones you name.
Field | Type | Description |
|---|---|---|
| array of strings | The parts of the theme to take. Default: every section, |
| string | Optional. The draft's |
Each section you apply replaces that part of the draft with the theme's. Applying a page replaces its components, with new ids, so read the draft again before you edit them. Applying
customCssreplaces your custom CSS blocks.warningssays when either happened.To borrow a theme's look without its layout, apply only
theme.colors,theme.darkColors,theme.fonts,theme.radiusandtheme.density.Name your sections. Without
sections, every section is applied,brandandmetaincluded: the draft gets the theme's placeholder name and tagline instead of yours, such asHelp Center, loses your logo, dark-mode logo and favicon, and takes the theme's SEO title and description, with the no-index setting off. Once published, that placeholder name becomes your help center's name. The template editor leavesbrandout unless someone ticks it.The draft's
preset_keybecomes the theme's key, even when you apply only some sections. Nothing changes for visitors until you publish.
Edit the draft
/v1/template/draftEdit the draft with a batch of up to 50 operations, applied in order and all or nothing.
Field | Type | Description |
|---|---|---|
| array | Required. 1 to 50 operations, each an object with an |
| string | Optional. The draft's |
In order. Each operation sees what the ones before it did. Give a new container your own
id, and you can add components into it in the same batch.All or nothing. The first operation that can't be applied fails the whole batch with
422 Unprocessable Entityandtemplate_invalid, and nothing is saved, not even the operations before it.The template editor's rules. A component goes only on the pages and in the regions it's made for, a component that's allowed once per region stays unique, a container takes only the component types it accepts, and a setting takes only the values the template editor offers.
What you get back.
resultshas one entry per operation, in order:operation(its position in the batch, from 0),op, and what it did, such as theidof a new component, CSS block or script, or thepathsasetchanged.templatecarries the newetag.Warnings.
warningspoints out what is allowed but probably not what you want, such as a script that every visitor will run, a Hero with an image background and no image, a Logo that shows an image whenbrand.logoAssetIdis empty, or a batch that left the draft as it was.
Operations
Operation | Fields | What it does |
|---|---|---|
|
| Changes document settings: |
|
| Adds a component. Settings you leave out in |
|
| Changes some of a component's settings. |
|
| Removes a component and everything inside it. |
|
| Moves a component on its page: to another region, into a container with |
|
| Styles a region: |
|
|
|
|
| Copies a region that isn't synced from one page to others, once. Later edits don't travel. |
|
| Adds a labelled custom CSS block. The stylesheet is rebuilt from the enabled blocks, in order. |
|
| Changes a block, or moves it with |
|
| Deletes a block. |
|
| Injects a script at |
|
| Changes a script. Sending a different body field switches it to that kind. |
|
| Deletes a script. |
|
| Replaces the whole menu: up to 30 items, each |
Every operation refuses fields it doesn't know, and the error names them.
A custom CSS block, and all blocks together, can hold up to 204,800 bytes. A block that would render as nothing is refused.
@import,@charset,@namespace,expression(),behavior:andjavascript:URLs are removed when the page renders, and a warning says so. See Custom CSS reference: selectors and examples.A script body can hold up to 51,200 bytes.
When an operation fails
A batch that can't be applied answers 422, and nothing is saved:
{
"status": "error",
"code": "template_invalid",
"message": "Nothing was saved. operation 1 (set), theme.colors.primary: must be a hex colour like \"#635bff\" (or \"#rrggbbaa\") (got \"blue\")",
"errors": [
{
"operation": 1,
"op": "set",
"path": "theme.colors.primary",
"message": "must be a hex colour like \"#635bff\" (or \"#rrggbbaa\") (got \"blue\")"
}
]
}
errors lists what went wrong, and message sums it up in one paragraph. Each error names the operation (its position in the batch, from 0), the op, the path of the field or setting, and in its message what would have been accepted. One operation can report several errors, for example a set with two invalid colors. An unknown component, CSS block or script id inside a batch is a template_invalid error too, not a 404.
A request that isn't a batch at all, with no operations or more than 50 of them, answers 422 with "status": "validation_error" instead.
Synced regions whose pages differ
A synced region is one region that every page shares: edit it on any page, and the edit is copied to all six pages, under the same component ids. header and footer are often synced.
A synced region can still hold different content on different pages. Some themes, for example, give category and article pages a header with a search field that the home page doesn't have. Editing such a region would copy one page's version over all the others, so the API refuses the edit until you choose what should happen. The outline lists these regions in syncedButDiverged.regions, and the refusal says how to choose:
{
"status": "error",
"code": "template_invalid",
"message": "Nothing was saved. operation 0 (update_component), region: the header region is synced (one header shared by every page), but its pages currently differ: category, article have a different header from the home page. Editing it would copy the home page's header over all of them. Choose first, in this batch or a new one: {\"op\": \"sync_region\", \"region\": \"header\", \"enabled\": false} to keep each page's header and edit them separately (then name the page on each edit), or {\"op\": \"sync_region\", \"region\": \"header\", \"enabled\": true, \"source_page\": \"<page>\"} to make every page use that page's header.",
"errors": [
{
"operation": 0,
"op": "update_component",
"path": "region",
"message": "the header region is synced (one header shared by every page), but its pages currently differ: category, article have a different header from the home page. Editing it would copy the home page's header over all of them. Choose first, in this batch or a new one: {\"op\": \"sync_region\", \"region\": \"header\", \"enabled\": false} to keep each page's header and edit them separately (then name the page on each edit), or {\"op\": \"sync_region\", \"region\": \"header\", \"enabled\": true, \"source_page\": \"<page>\"} to make every page use that page's header."
}
]
}
Keep each page's version:
{"op": "sync_region", "region": "header", "enabled": false}. Each page keeps its own copy, and the component ids don't change. Where the same id is on several pages, name thepagein the edit: without it, the edit is refused and asks which page you mean.Make every page use one page's version:
{"op": "sync_region", "region": "header", "enabled": true, "source_page": "category"}. The other pages lose what their own version had, and a warning says so.
You can choose and edit in the same batch:
{
"operations": [
{
"op": "sync_region",
"region": "header",
"enabled": false
},
{
"op": "update_component",
"id": "01M3SC61SPBDHVRP65G50W2F8J",
"page": "home",
"config": {
"variant": "text-only"
}
}
]
}
Strings are stored as you send them
Unlike the rest of the API (see Requests and responses), the design endpoints don't trim strings and don't turn empty strings into null:
An empty string clears a text.
{"op": "update_component", "id": "01M3SBMGRXRW19W78F2THFE327", "config": {"subheading": ""}}removes a Hero's subheading.nullputs a component setting back to its default. The same operation with"subheading": nullbrings back the Hero's default subheading, "Search articles, browse categories, or contact us."Settings that can't be empty refuse it.
brand.nameandmeta.titlerefuse an empty string,nulland a string of only spaces.Spaces and line breaks are kept in Markdown, CSS and scripts, so an indented code line in a RichText component stays indented.
Avoid overwriting someone else's changes
People can edit the draft in the template editor while your code works on it. Every write takes an optional etag to keep you from building on a draft you haven't seen:
Read the
etag:template.draft.etagfromGET /v1/templateor any write, oretagfromGET /v1/template/draft. The quotes inside it are part of the value: send it back exactly, as a JSON string.Send it with your write. If the draft changed since you read it, because someone saved in the template editor, another integration wrote to it or someone published, nothing changes and the API answers
412 Precondition Failedwithstale_draftand the currentetag.Read the draft again, make your change on top of what is there now, and retry.
{
"status": "error",
"code": "stale_draft",
"message": "The draft changed since you read it (someone edited it in the Template Editor, or another agent did), so nothing was changed. Read the template again and re-apply your change on top of what is there now.",
"etag": "\"223-1790779485000-880c4754\""
}
Every write returns the draft's new
etagintemplate.draft.etag, so a series of writes can pass it along without reading in between.Without
etag, a write applies to the draft as it is at that moment. Two writes never mix, but yours can land on changes you haven't seen.Every publish starts a new draft, so an
etagfrom before a publish always answers412.The template editor checks the same
etag. If a person saves there after your write, the editor says This draft was changed elsewhere. and offers Reload, which keeps your changes, or Overwrite, which replaces them with theirs.
Discard the draft
/v1/template/draft/discardThrow the draft's changes away: the draft goes back to the live design.
Discarding resets the draft to the live design, like Reset draft in the template editor. Every unpublished change is lost, including changes people made in the template editor, and it can't be undone. The draft keeps its id, and its preset_key stays as it was. The body is optional: send etag to discard only the draft you read.
Publish the draft
/v1/template/publishPublish the draft: visitors see it at once, it's recorded as a version, and a new draft starts from it.
Field | Type | Description |
|---|---|---|
| string | Optional. A note of up to 300 characters that says what changed. It becomes the version's |
| string | Optional, and recommended: the |
The whole draft goes live, including changes people made in the template editor. The version that was live becomes an earlier version, and the draft becomes the new live version, keeping the draft's
id. A new draft starts from it, with a newidandetag.The brand name follows.
brand.namebecomes the help center's name everywhere, including the widget, contact form emails and the API.Nothing to publish. A draft that is the same as the live design answers
409 Conflictwithnothing_to_publish.Publish after the review. Publishing changes what visitors see, so publish once the person responsible for the design has reviewed the draft and wants it live (see Publish your design).
List the published versions
/v1/template/versionsList the published versions of the design, newest first: the live one and up to 49 earlier ones.
The list is newest first and holds up to 50 versions: the live one and earlier ones.
Field | Type | Description |
|---|---|---|
| integer | The version. A draft keeps its id when it's published. |
| string |
|
| boolean |
|
| string or null | The note it was published with. |
| string | The draft's |
| string | When it was published, ISO 8601 with an offset. |
| string or null | The name of the team member who published it, or |
Restore a version into the draft
/v1/template/versions/{versionId}/restoreLoad an earlier version into the draft, for review. It goes live only when you publish.
It goes to the draft, not live. The version, with its translated texts, replaces what the draft holds. Visitors see it only after you publish, so a restore is reviewed like any other change. In the template editor, Restore publishes at once; over the API it doesn't.
Any version in the list works, the live one included. An id that isn't a version of this help center answers
404withversion_not_found.The draft's changes are replaced. The body is optional: send
etagto restore only onto the draft you read.
Try it end to end
This program reads the design, changes the draft's colors with the etag it read, reads again and retries if someone changed the draft in between, and prints the link to review the result. It doesn't publish. Run it with a Read & write key created with design access in HELPCENTER_API_KEY:
// Change the theme colors in the design draft, then print the link to review it.
// Visitors see nothing until someone publishes. Node.js 18 or later, no dependencies.
const API = 'https://api.helpcenter.io/v1';
const COLORS = { primary: '#1d4ed8', accent: '#dbeafe' };
async function api(method, path, body) {
const res = await fetch(API + path, {
method,
headers: {
apikey: process.env.HELPCENTER_API_KEY,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: body === undefined ? undefined : JSON.stringify(body),
});
return { status: res.status, data: await res.json() };
}
async function main() {
for (let attempt = 1; attempt <= 3; attempt++) {
// 1. Read where the design stands: the outline, and the draft's etag.
const read = await api('GET', '/template');
if (read.status !== 200) throw new Error(`GET /template: ${read.status} ${JSON.stringify(read.data)}`);
console.log(`Primary color in the draft: ${read.data.outline.theme.colors.primary}`);
// 2. Change the colors, but only if nobody changed the draft since you read it.
const edit = await api('PATCH', '/template/draft', {
etag: read.data.template.draft.etag,
operations: [{ op: 'set', path: 'theme.colors', value: COLORS }],
});
if (edit.status === 412) {
console.log('The draft changed since it was read. Reading it again.');
continue;
}
if (edit.status !== 200) throw new Error(`PATCH /template/draft: ${edit.status} ${JSON.stringify(edit.data)}`);
console.log(`Changed ${edit.data.results[0].paths.join(', ')}`);
for (const warning of edit.data.warnings) console.log(`Warning: ${warning}`);
// 3. A person reviews the draft in the template editor, then publishes it.
console.log(`Review it in the template editor: ${edit.data.template.urls.customizer}`);
return;
}
throw new Error('The draft kept changing. Try again later.');
}
main().catch((err) => {
console.error(err.message);
process.exit(1);
});
# Change the theme colors in the design draft, then print the link to review it.
# Visitors see nothing until someone publishes. Python 3.8 or later, standard library only.
import json
import os
import sys
import urllib.error
import urllib.request
API = "https://api.helpcenter.io/v1"
COLORS = {"primary": "#1d4ed8", "accent": "#dbeafe"}
def api(method, path, body=None):
request = urllib.request.Request(
API + path,
method=method,
data=None if body is None else json.dumps(body).encode(),
headers={
"apikey": os.environ["HELPCENTER_API_KEY"],
"Accept": "application/json",
"Content-Type": "application/json",
},
)
try:
with urllib.request.urlopen(request) as response:
return response.status, json.load(response)
except urllib.error.HTTPError as err:
return err.code, json.loads(err.read() or b"{}")
for attempt in range(3):
# 1. Read where the design stands: the outline, and the draft's etag.
status, read = api("GET", "/template")
if status != 200:
sys.exit(f"GET /template: {status} {read}")
print(f"Primary color in the draft: {read['outline']['theme']['colors']['primary']}")
# 2. Change the colors, but only if nobody changed the draft since you read it.
status, edit = api("PATCH", "/template/draft", {
"etag": read["template"]["draft"]["etag"],
"operations": [{"op": "set", "path": "theme.colors", "value": COLORS}],
})
if status == 412:
print("The draft changed since it was read. Reading it again.")
continue
if status != 200:
sys.exit(f"PATCH /template/draft: {status} {edit}")
print("Changed " + ", ".join(edit["results"][0]["paths"]))
for warning in edit["warnings"]:
print(f"Warning: {warning}")
# 3. A person reviews the draft in the template editor, then publishes it.
print(f"Review it in the template editor: {edit['template']['urls']['customizer']}")
break
else:
sys.exit("The draft kept changing. Try again later.")
<?php
// Change the theme colors in the design draft, then print the link to review it.
// Visitors see nothing until someone publishes. PHP 8 or later with the curl extension.
const API = 'https://api.helpcenter.io/v1';
const COLORS = ['primary' => '#1d4ed8', 'accent' => '#dbeafe'];
function api(string $method, string $path, ?array $body = null): array
{
$ch = curl_init(API . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'apikey: ' . getenv('HELPCENTER_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$data = json_decode((string) curl_exec($ch), true);
return [curl_getinfo($ch, CURLINFO_RESPONSE_CODE), $data];
}
for ($attempt = 1; $attempt <= 3; $attempt++) {
// 1. Read where the design stands: the outline, and the draft's etag.
[$status, $read] = api('GET', '/template');
if ($status !== 200) {
fwrite(STDERR, "GET /template: $status " . json_encode($read) . "\n");
exit(1);
}
echo 'Primary color in the draft: ' . $read['outline']['theme']['colors']['primary'] . "\n";
// 2. Change the colors, but only if nobody changed the draft since you read it.
[$status, $edit] = api('PATCH', '/template/draft', [
'etag' => $read['template']['draft']['etag'],
'operations' => [['op' => 'set', 'path' => 'theme.colors', 'value' => COLORS]],
]);
if ($status === 412) {
echo "The draft changed since it was read. Reading it again.\n";
continue;
}
if ($status !== 200) {
fwrite(STDERR, "PATCH /template/draft: $status " . json_encode($edit) . "\n");
exit(1);
}
echo 'Changed ' . implode(', ', $edit['results'][0]['paths']) . "\n";
foreach ($edit['warnings'] as $warning) {
echo "Warning: $warning\n";
}
// 3. A person reviews the draft in the template editor, then publishes it.
echo 'Review it in the template editor: ' . $edit['template']['urls']['customizer'] . "\n";
exit(0);
}
fwrite(STDERR, "The draft kept changing. Try again later.\n");
exit(1);
It prints:
Primary color in the draft: #635bff
Changed theme.colors.primary, theme.colors.accent
Review it in the template editor: https://helpcenter.io/app/sites/acme/customizer-v2
After the review, when the person asks for it, publish the draft:
curl -X POST https://api.helpcenter.io/v1/template/publish \
-H "apikey: $HELPCENTER_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"message": "New brand colors"}'
Errors
Status | Code | When |
|---|---|---|
|
| An OAuth token reaches several help centers and the request named none (see Choose which help center a request acts on). |
| None: | No key or token, or one the API doesn't accept. |
|
| The credential lacks |
|
| The person behind the credential can't edit the design in the dashboard. |
|
| An OAuth token named a help center it doesn't reach. |
|
| No theme with that key. The message lists the keys. |
|
| Reading a component, script or custom CSS block that isn't in the draft. |
|
| No version with that id on this help center. |
|
| The help center doesn't use the template editor yet. |
|
| Publishing a draft that is the same as the live design. |
|
| The draft changed since the |
|
| An operation couldn't be applied. Nothing was saved. |
| None: | The request itself is invalid: an unknown |
| None | Too many requests. Wait for |
For the shapes that every endpoint shares, see Errors.