When a request fails, the HTTP status says what kind of problem it is. Most error bodies add a message written for people, and many add a machine-readable code. This page lists the error shapes and codes the API returns, what each one means and what to do about it, and ends with the problems developers run into most.
How to handle an error
Check the status code first. A refusal from the web servers in front of the API isn't JSON, so look at the status before you parse the body.
Branch on
codewhen the body has one. Change sets usereasoninstead.Show
messageto people, but don't parse it. Messages explain the problem and can change.Expect JSON from the API. Every error the API answers is JSON, whatever you send in
Accept, and it never answers an error with a redirect. Only the web servers in front of it answer otherwise: a request body over 32 MB, or more requests than they allow from one address (see Rate limits).
For example, creating an article with a Read only key:
curl https://api.helpcenter.io/v1/articles \
-H "apikey: $HELPCENTER_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{ "title": { "en": "Shipping times" } }'
The API answers 403 Forbidden:
{
"status": "error",
"code": "insufficient_scope",
"message": "This action requires the content.write scope."
}
Error shapes
Body | Status | Where you meet it |
|---|---|---|
| 401 | The request has no key or token, or one the API doesn't accept. |
| 400, 401, 403, 404, 409, 422, 500 | Most errors from an endpoint, such as |
| 400, 402, 403, 404, 409, 410, 412, 422, 429, 503 | Errors with a machine-readable code, sometimes with more fields. See Error codes below. |
| 402, 409 | Change sets. |
| 422 | Validation on most endpoints, including |
| 400 | Validation on |
| 400 | Validation on |
| 405, 429, 500 | General errors: a method the path doesn't take, a rate limit, a failure on HelpCenter.io's side. |
| 404 | An unknown path. |
An HTML page | 413, 429 | A refusal from the web servers in front of the API: a request body over 32 MB, or more requests than they allow from one address. |
Validation errors
Validation errors list the problems by field: every key of errors is a field you sent, and every value a list of messages. The envelope around them depends on the endpoint. Most endpoints answer 422 Unprocessable Entity, as here for a category name sent as a string:
{
"status": "validation_error",
"message": "The request could not be accepted.",
"errors": {
"name": [
"The name must be a {locale: text} map — for example {\"en\": \"Billing\"}."
]
}
}
GET /v1/articles answers 400 Bad Request without a message, here for limit=500:
{
"status": "validation_error",
"errors": {
"limit": ["The limit field must not be greater than 100."]
}
}
POST /v1/articles answers 400 with the fields at the top level, here for an article without a title:
{"title": ["The title field is required."]}
GET /v1/categories, GET /v1/images and PATCH /v1/articles/{id} answer 422 with the same envelope as the other endpoints. Here GET /v1/categories?limit=500:
{
"status": "validation_error",
"message": "The request could not be accepted.",
"errors": {
"limit": ["The limit field must not be greater than 100."]
}
}
Bulk requests validate every item on its own, inside a 207 response. Read results: the top-level status is success even when every item failed.
{
"status": "success",
"results": [
{
"index": 0,
"status": "error",
"external_id": null,
"error": {
"code": "validation_error",
"message": "The article could not be accepted.",
"errors": {"title": ["The title field must be an array."]}
}
},
{
"index": 1,
"status": "error",
"error": {
"code": "invalid_item",
"message": "Each item must be an article object."
}
},
{
"index": 2,
"status": "error",
"external_id": null,
"error": {
"code": "invalid_argument",
"message": "Unsupported language key \"fr\" provided."
}
}
],
"meta": {"total": 3, "created": 0, "updated": 0, "failed": 3}
}
Status codes
Status | What it means | What to do |
|---|---|---|
| Validation on | Fix the request. |
| No credential, or one the API doesn't accept: a missing, mistyped, deleted or expired key, a key whose creator is no longer an Owner or Admin of the help center or whose HelpCenter.io account was deleted, or an OAuth token that expired or was revoked. | Check the |
| The help center doesn't have this feature: | Change the plan, or for change sets on Catalyst, request early-preview access. Retrying doesn't help. |
| The credential is valid but may not do this: | See the code in the table below. |
| Nothing with that id exists on the help center your credential acts on. That includes ids from other help centers and items in the Trash. A key whose help center was deleted gets | Check the id, and which help center your key belongs to: |
| The path exists, but not with this method. | Check the method in the reference. |
| The request clashes with the current state: | Read the body: it says what clashed, and often what to send instead. |
| An upload URL expired or was already used, or | Create another upload URL with |
|
| Read the draft again, make your change on top of it, and retry with the new |
| The request body is over 32 MB. The web servers in front of the API refuse it, with a page that isn't JSON. | Send fewer or smaller files per request. |
| Validation failed, or the request is valid but can't be carried out, such as | Fix the fields in |
| You hit a rate limit, or a limit with a code: | Wait for |
| Something failed on HelpCenter.io's side. The body is | Retry later. If it keeps happening, contact support with the method, path, time and body of the request, never your key. |
|
| Retry after |
401, 403 or 404?
401 means the API doesn't know who you are. 403 means it knows, but your credential may not do this. 404 means there is nothing with that id on the help center your credential acts on: an id from another help center always answers 404, never 403, so an error never confirms that an id exists somewhere else.
{"status": "error", "message": "Article not found."}
An unknown id answers 404 with a JSON body on every route, such as Article not found. or Category not found., and an unknown path answers {"status": "error", "message": "Not found."}.
Error codes
These codes appear in the code field of REST API errors. The last five, marked "reason", appear in the reason field of change set errors.
Code | Status | What it means | What to do |
|---|---|---|---|
| 403 | The credential lacks the scope this endpoint needs. The message names it: | Use a Read & write key (for team notes, one with team notes access; for the design, one with design access; for the custom domain, one that an Owner or Admin allowed to manage the custom domain, with Allow managing the custom domain in Settings, under API keys), or ask the user to approve that scope for your app. See Scopes. |
| 400 | An OAuth token reaches more than one help center, and the request didn't name one. | Send |
| 403 | The help center you named isn't one this token reaches, or its id isn't a number. | Use an id from |
| 402 | A request through the MCP server for a help center that isn't on Growth or Catalyst. Adds | Change the plan at |
| 403 | The person behind the credential can no longer see team notes on this help center, which takes the Owner, Admin, Editor or Translator role. | Use a credential of someone who has that access. |
| 403 | The person behind the credential can no longer edit this help center's design in the dashboard, which takes the Owner, Admin or Editor role. | Use a credential of someone who has that access. See Design API. |
| 429 | More than 6 analytics requests in a minute with this credential. | Wait for |
| 503 | The analytics are temporarily unavailable. Comes with | Try again in five minutes. |
| 409 | You are deleting a category that still holds articles, drafts included, or child categories. Adds | Send |
| 403 | The upload URL was changed, or isn't known. | Use the exact |
| 403 | The credential that created the upload URL can no longer upload images to this help center. | Create another with a credential that can. |
| 410 | The upload URL is past its | Create another. |
| 410 | The upload URL was already used. Each one takes a single upload. | Create another. |
| 403 | Staged changes aren't available on this help center: it isn't on the Catalyst plan. | Update the live article with |
| 422 | You staged changes on an article that isn't published, so there is no live version to keep them apart from. | Update the article with |
| 422 | You staged changes on an article that was unpublished after changes were staged. Those staged changes are kept. | Publish the article again, or update it with |
| 409 | The staged changes changed since you read them. Adds | Read them again, then retry with the new value. |
| 409 | The live article changed after you staged your edits. Adds | Read the article again, or publish with |
| 422 | You sent | Leave it out. Add the article to a change set with |
| 409 | The help center doesn't use the template editor yet, so the design API can't reach it. Adds | Ask the owner to switch it to the template editor, at |
| 409 | You published a design draft that is the same as the live design. | Change the draft first. |
| 412 | The design draft changed since the | Read the draft again, make your change on top of it, and retry. |
| 422 | An operation in a design batch couldn't be applied, so nothing was saved. | Fix the operation the error names, and send the batch again. |
| 404 | No theme with that key. The message lists the keys. | Use a key from |
| 404 | You read a component, script or custom CSS block that isn't in the design draft. | Read the current ids with |
| 404 | No published version of the design with that id on this help center. | Use an id from |
| 403 | An OAuth token whose person's role on the help center doesn't allow this action. Adds | Ask one of the help center's admins to change the person's role, or use a credential of someone who has it. |
| 403 | An OAuth token on | Use an API key of the help center. |
| 403 | The help center's plan doesn't include a custom domain. Adds | The owner upgrades on the billing page. |
| 409 | Another help center already uses that domain. | Remove it there first, or contact support. See Custom domain. |
| 409 | Another change to the help center's domain is still running. Nothing was changed. | Retry shortly. See Custom domain. |
| 429 | The help center has made 4 domain changes in the last month. Nothing was changed. Comes with | Wait for |
| 409 | The import is queued or running, so it can't be changed, run or cancelled now. | Follow it with |
| 409 | The import was cancelled. | Scan its address again with |
| 409 | You ran an import whose address the importer can't read ( | Move the content another way. |
| 409 | The help center's main language changed after the import wrote into it, so a run would write nothing. | Switch the main language back in Settings, under Localization. |
| 422 | An AI agent's key sent credentials for an import. It imports public sources only. | Leave the credentials out; the owner can connect a private source from the dashboard. |
| 422 |
| Scan again in a few minutes, or after 00:00 UTC; or file a migration request. |
| 422 | The import source is private: a Zendesk help center behind sign-in, or a HelpDocs site that needs its API key. Adds | Send the credentials the message names (not with an AI agent's key). |
| 422 |
| Follow the message; HelpCenter.io's migration team can move such a source. |
| 422 | A redirect from that path would never be used: the help center answers the path itself. | Redirect another path. |
| 422 | A redirect points at a page of the help center that doesn't exist now, or isn't published and public. | Point it at the page's current address. |
| 422 | A redirect points at another site, and the key isn't allowed to redirect to other sites. An AI agent's key never is. | Point it at a page of the help center. A redirect to another site needs a Read & write key that an Owner or Admin allowed to redirect to other sites, with Allow redirects to other sites in Settings, under API keys. |
| 422 | The redirect would make a loop, or a chain of more than 3 redirects. | Point each redirect at the final page. See Redirects. |
| 422, 429 | 422: an unclaimed help center is full (500 articles, 100 categories or 500 MB of images and files; | |
| 422 | A help center created without an account was given a name that contains a protected brand name. | Use your user's own product or company name. |
| 503 | No help center can be created without an account right now: as many are open as HelpCenter.io allows, or many are being created ( | Try again later, or file a migration request. For a migration request, try later or with fewer sources that need a new help center. |
| 403 | An AI agent's key asked for something outside what it serves: its help center's content (articles, categories, images, imports, staged changes, change sets), its design ( | Use only those endpoints. Everything else is your user's, with an API key they create in Settings, under API keys. |
| 403 | An AI agent's key on redirects or the custom domain before the help center's owner has claimed it, verified their email address and activated a plan. Adds | Pass |
| 403 | An AI agent's key tried to change the design's scripts or HTML. Nothing was changed. Adds | Leave those parts out; the help center's team makes them in the Template Editor. |
| 422 | An AI agent's key tried to load a file in CSS, or a Hero background, from another host. Nothing was changed. The message names the address. | Use a path on the help center, a |
| 409 | An AI agent's key wrote to a help center that an open migration request holds. Reads still work. | Wait for the request to end. |
| 409 | An AI agent's key tried to delete, or file a migration request for, a help center its owner has claimed. | Nothing to do: it belongs to its owner. |
| 409 | A migration request named an | File the request without that source. |
| 410 | An AI agent's key whose help center expired before anyone claimed it, and was deleted. Adds | Start again with |
| 402 | Change sets aren't available on this help center: it isn't on Catalyst, or doesn't have early-preview access yet. | Change the plan. On Catalyst, request access: in the dashboard, click Change sets, then Request access. Retrying doesn't help. |
| 409 | The change set already holds 500 items. Items added before the limit stay added. | Put the rest in a second change set. |
| 409 | The help center already has 25 open change sets. | Publish or delete one first. |
| 409 | The change set is being published, or was published, so its contents can't change. | Create another change set. |
| 409 | You sent | Publish it now, without |
For example, deleting a category that still holds an article:
{
"status": "error",
"code": "category_not_empty",
"message": "This category still holds content. Re-send with ?on_orphan=uncategorize or ?on_orphan=reparent to say where it should go.",
"articles_count": 1,
"children_count": 0
}
Codes inside results
Bulk responses report each failed item's code in results[].error.code. Change sets report their items' problems with a reason.
Where | Code | What it means |
|---|---|---|
|
| The item isn't an article object. |
|
| The item failed validation. |
|
| The item uses a language code your help center doesn't have. |
|
| Another request is importing the same |
|
| The article couldn't be saved. |
|
| An unclaimed help center is full. Stop, and file a migration request for the rest. |
|
| The redirect couldn't be saved, as for |
|
| The image couldn't be stored. Retry it. |
|
| The image at the |
Change set |
| An item can't be published: the live article changed after staging, or the article or category was deleted. The change set can't publish until you fix or remove it. |
Change set |
| Worth a look, but publishing isn't blocked. |
Change set |
| No article or category with that id on the help center. A published article with nothing staged is rejected with a sentence instead. |
Errors outside the REST API
The other surfaces answer in their own formats.
OAuth
The authorization server on https://helpcenter.io answers with error and error_description, as OAuth 2.1 defines. See OAuth 2.1 for apps and AI clients.
| Status | When |
|---|---|---|
| 400, 404 | A client sent no |
| 401 | Client authentication failed, the client isn't known, or, at the authorization endpoint, the |
| 400 | An authorization code or refresh token that is invalid, expired, already used, revoked or issued to another client; at the token endpoint, a |
| Redirect | The authorization asked for a scope that doesn't exist. |
| 400 | A grant other than |
| Redirect | The person clicked Deny on the consent screen. |
| 400 | A |
| 400 | Client registration with missing or invalid |
| 400 | Client registration with an unsupported |
| 429 | More than 10 client registration requests in an hour from one IP address, refused ones included. |
GET https://helpcenter.io/oauth/userinfo answers a bad token with 401 and {"error": {"code": "invalid_token", "message": …, "status": 401}}.
MCP server
https://mcp.helpcenter.io refuses a request before any tool runs with an HTTP status, and for a credential problem also a WWW-Authenticate header. Errors from the API behind a tool, such as plan_upgrade_required, reach the agent as the tool's result, with an explanation. See Connect an AI assistant to your help center.
Status | When |
|---|---|
| No credential: sign in with OAuth, or send an API key as |
| The OAuth token was rejected, for example because it expired or wasn't issued for |
| The token lacks a permission the tool needs, such as |
| An MCP protocol version the server doesn't support. |
| The request lacks |
Public MCP server
The read-only server at https://<your help center>/mcp answers with JSON-RPC errors. See The public MCP server of a help center.
Error | When |
|---|---|
| The help center has no public MCP server: it isn't public or hasn't been published yet, is limited to certain IP addresses or to your app, hides from search engines, has the server switched off, or isn't live. |
| More than 120 requests a minute from your address, or 1,200 for the help center. Wait for |
| A request body over 64 KiB. |
| The body isn't valid JSON. |
| A batch of messages (send one per request), or an unsupported |
| An unknown method. |
| An unknown tool, or protocol parameters that don't match the request. Invalid tool arguments come back as a tool result with |
| The |
Widget and embeds
The widget's requests to https://embed.helpcenter.io answer with status, message and code. See Content Security Policy and allowed origins.
Code | Status | When |
|---|---|---|
| 410 | No widget has the |
| 403 | The help center is private, or public but not published yet, and the request had no valid JWT. A reader's JWT doesn't open a public help center that isn't published yet: only its team, signed in, can see it. See Sign readers into the widget with a JWT. |
| 403 | The help center only opens from certain IP addresses, and this visitor's isn't one of them. |
| 403 | The help center is unavailable, so FAQ sections show nothing. |
| 403 | A FAQ section was asked for on a public help center that hasn't been published yet. It serves FAQ sections once its team publishes it. |
Single sign-on
Single sign-on returns no error codes. /sso/jwt answers in plain text: 400 Missing jwt parameter., 401 Invalid SSO token. (a claim of the wrong type included), 403 Single sign-on is switched off for this help center., or 500 SSO is not properly configured. Most other problems send the reader to the HelpCenter.io sign-in page. See Troubleshoot single sign-on.
Troubleshooting
Symptom | Cause | Fix |
|---|---|---|
An HTML page where you expected JSON | The web servers in front of the API refused the request: a body over 32 MB, or more requests than they allow from your address. | Send smaller requests, or slow down. Check the status before you parse. |
"The title field is required." although you sent a title | The request had no | Add the header. |
| A header other than | Send |
| The key belongs to another help center, or the article is in the Trash. | Check |
|
| Send |
| Single-article, staged, version and change set reads count against the 120 writes; exports, bulk requests and image uploads share a limit per IP address; the edge allows about 2 requests a second. | See Rate limits. |
| A title that is empty or only spaces in one of the languages you sent. | Send text in every title you include; leave a language out to leave it alone. |
| The key wasn't allowed to manage the custom domain. | Ask an Owner or Admin of the help center to tick Allow managing the custom domain for the key in Settings, under API keys. |
| The | Point it at a page of the help center, or ask an Owner or Admin to tick Allow redirects to other sites for the key. |