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 every error shape and every code 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 few errors aren'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.Send
Accept: application/jsonwith every request. It turns most errors that would be HTML pages into JSON, and stops validation errors from becoming redirects.
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. |
| 400 | Validation on |
| 400 | Validation on |
| 422 | Validation on |
| 405, 429, 500 | General errors, when you send |
An HTML page | 404, 413, 429 | An unknown path; an unknown id on |
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 without a status, and only when you send Accept: application/json. Without that header they redirect to https://api.helpcenter.io with a 302. Here GET /v1/categories?limit=500:
{
"message": "The limit field must not be greater than 100.",
"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 failed on | Send the header. Don't follow redirects from the API: the target is a welcome message, not your result. |
| Validation on | Fix the request. |
| No credential, or one the API doesn't accept: a missing, mistyped or deleted key, the key of a person 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. | Create another with |
|
| Read the draft again, make your change on top of it, and retry with the new |
| The request body is over 32 MB. | 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. | Wait for |
| Something failed on HelpCenter.io's side. With | 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."}
Some 404 answers are HTML pages, whatever you send in Accept: an unknown path, and an unknown id on PATCH or DELETE /v1/articles/{id}, PATCH or DELETE /v1/categories/{id} and PUT /v1/categories/{id}/articles/order. Check the status code before you parse the body.
Error codes
These codes appear in the code field of REST API errors. The last four, 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), 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 Catalyst, or doesn't have early-preview access yet. | 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 |
| 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, or on Catalyst request access as for |
| 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. |
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. |
|
| 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, 401, 404 | A public client sent no |
| 401 | Client authentication failed, the client isn't known, or the |
| 400 | The |
| 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, 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 and the request had no valid JWT. 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. |
Single sign-on
Single sign-on returns no error codes. /sso/jwt answers in plain text: 400 Missing jwt parameter., 401 Invalid SSO token., or 500 SSO is not properly configured. Most other problems send the reader to the HelpCenter.io sign-in page, and a claim of the wrong type fails with an error page. See Troubleshoot single sign-on.
Troubleshooting
Symptom | Cause | Fix |
|---|---|---|
An HTML page where you expected JSON | No | Send the header, check the path and the id, and check the status before you parse. |
A write answers | Validation failed, and your client followed the | Send |
"The title field is required." although you sent a title | The request had no | Add the header. |
| A header other than | Send |
| Change sets accept only API keys, not OAuth tokens. | Use an API key for change sets. |
| 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. |