Getting started

Errors

Export
Download Markdown Use with AI

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

  1. Check the status code first. A few errors aren't JSON, so look at the status before you parse the body.

  2. Branch on code when the body has one. Change sets use reason instead.

  3. Show message to people, but don't parse it. Messages explain the problem and can change.

  4. Send Accept: application/json with 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

{"status": "unauthorized"}

401

The request has no key or token, or one the API doesn't accept.

{"status": "error", "message": …}

400, 401, 403, 404, 409, 422, 500

Most errors from an endpoint, such as Article not found., and an image that couldn't be stored (500).

{"status": "error", "code": …, "message": …}

400, 402, 403, 404, 409, 410, 412, 422, 429, 503

Errors with a machine-readable code, sometimes with more fields. See Error codes below.

{"status": "error", "reason": …, "message": …}

402, 409

Change sets.

{"status": "validation_error", "message": …, "errors": {…}}

422

Validation on most endpoints.

{"status": "validation_error", "errors": {…}}

400

Validation on GET /v1/articles.

{"<field>": […]}

400

Validation on POST /v1/articles.

{"message": …, "errors": {…}}

422

Validation on GET /v1/categories, GET /v1/images and PATCH /v1/articles/{id}, when you send Accept: application/json. Without it, a 302 redirect.

{"message": …}

405, 429, 500

General errors, when you send Accept: application/json. Without it, an HTML page.

An HTML page

404, 413, 429

An unknown path; an unknown id on PATCH or DELETE /v1/articles/{id}, PATCH or DELETE /v1/categories/{id} and PUT /v1/categories/{id}/articles/order; a request body over 32 MB; the edge rate limit.

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

302 Found

Validation failed on GET /v1/categories, GET /v1/images or PATCH /v1/articles/{id}, and the request had no Accept: application/json.

Send the header. Don't follow redirects from the API: the target is a welcome message, not your result.

400 Bad Request

Validation on GET and POST /v1/articles, a language code your help center doesn't have (Unsupported language key "fr" provided.), or site_required.

Fix the request.

401 Unauthorized

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. /v1/change-sets also answers 401 with Unknown API key. to an OAuth token.

Check the apikey header, or refresh the token. Use an API key for change sets.

402 Payment Required

The help center doesn't have this feature: plan_upgrade_required for requests through the MCP server, CHANGE_SETS_UNAVAILABLE for change sets.

Change the plan, or for change sets on Catalyst, request early-preview access. Retrying doesn't help.

403 Forbidden

The credential is valid but may not do this: insufficient_scope, site_forbidden, notes_forbidden, design_forbidden, STAGING_UNAVAILABLE, upload_url_invalid or upload_url_revoked. Also asking for commenters' email addresses with a Read only key.

See the code in the table below.

404 Not Found

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 The site this API key belongs to is no longer available. The design endpoints answer with a code, listed below.

Check the id, and which help center your key belongs to: GET /v1/sites tells you.

405 Method Not Allowed

The path exists, but not with this method.

Check the method in the reference.

409 Conflict

The request clashes with the current state: category_not_empty, STALE_ETAG, BASE_CHANGED, template_migration_required, nothing_to_publish, a change set reason, an external_id that another article holds or that another request is importing, or an image that articles still use.

Read the body: it says what clashed, and often what to send instead.

410 Gone

An upload URL expired or was already used.

Create another with POST /v1/images/uploads.

412 Precondition Failed

stale_draft: the design draft changed since the etag you sent, so nothing changed.

Read the draft again, make your change on top of it, and retry with the new etag.

413 Payload Too Large

The request body is over 32 MB.

Send fewer or smaller files per request.

422 Unprocessable Entity

Validation failed, or the request is valid but can't be carried out, such as NOT_PUBLISHED or template_invalid.

Fix the fields in errors, or follow the message.

429 Too Many Requests

You hit a rate limit.

Wait for Retry-After seconds. See Rate limits.

500 Internal Server Error

Something failed on HelpCenter.io's side. With Accept: application/json, the body is {"message": "Server Error"}.

Retry later. If it keeps happening, contact support with the method, path, time and body of the request, never your key.

503 Service Unavailable

analytics_unavailable: the analytics are temporarily unavailable.

Retry after Retry-After seconds (300).

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

insufficient_scope

403

The credential lacks the scope this endpoint needs. The message names it: This action requires the content.write scope.

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.

site_required

400

An OAuth token reaches more than one help center, and the request didn't name one. sites lists the ids it can reach. An empty sites list means it reaches none.

Send X-HCio-Site or ?site= with one of the ids. An empty list has two causes. The person's account owns no help center: connect your app from an account that owns the help center, or use an API key from that help center. Or every help center they shared has since been deleted: connect again and share one.

site_forbidden

403

The help center you named isn't one this token reaches, or its id isn't a number.

Use an id from GET /v1/sites.

plan_upgrade_required

402

A request through the MCP server for a help center that isn't on Growth or Catalyst. Adds feature, upgrade_url and plans.

Change the plan at upgrade_url. Calls you make to the REST API directly with an API key aren't affected.

notes_forbidden

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.

design_forbidden

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.

analytics_rate_limited

429

More than 6 analytics requests in a minute with this credential.

Wait for Retry-After seconds, and store what you read: most figures change once a day.

analytics_unavailable

503

The analytics are temporarily unavailable. Comes with Retry-After: 300.

Try again in five minutes.

category_not_empty

409

You are deleting a category that still holds articles, drafts included, or child categories. Adds articles_count and children_count.

Send on_orphan=uncategorize or on_orphan=reparent.

upload_url_invalid

403

The upload URL was changed, or isn't known.

Use the exact upload_url, or create another.

upload_url_revoked

403

The credential that created the upload URL can no longer upload images to this help center.

Create another with a credential that can.

upload_url_expired

410

The upload URL is past its expires_at, 15 minutes after it was created.

Create another.

upload_url_used

410

The upload URL was already used. Each one takes a single upload.

Create another.

STAGING_UNAVAILABLE

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 PATCH /v1/articles/{id}. On Catalyst, request access: in the dashboard, click Change sets, then Request access.

NOT_PUBLISHED

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 PATCH /v1/articles/{id}.

ARTICLE_UNPUBLISHED

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 PATCH /v1/articles/{id}.

STALE_ETAG

409

The staged changes changed since you read them. Adds scope and the current etag or shared_etag.

Read them again, then retry with the new value.

BASE_CHANGED

409

The live article changed after you staged your edits. Adds locale.

Read the article again, or publish with "force": true to overwrite the live version.

CHANGE_SETS_UNAVAILABLE

422

You sent change_set_id while staging changes.

Leave it out. Add the article to a change set with POST /v1/change-sets/{id}/items.

template_migration_required

409

The help center doesn't use the template editor yet, so the design API can't reach it. Adds customizer_url.

Ask the owner to switch it to the template editor, at customizer_url.

nothing_to_publish

409

You published a design draft that is the same as the live design.

Change the draft first.

stale_draft

412

The design draft changed since the etag you sent. Adds the current etag.

Read the draft again, make your change on top of it, and retry.

template_invalid

422

An operation in a design batch couldn't be applied, so nothing was saved. errors lists each problem with its operation, op, path and message.

Fix the operation the error names, and send the batch again.

unknown_theme

404

No theme with that key. The message lists the keys.

Use a key from GET /v1/template/themes.

component_not_found, not_found

404

You read a component, script or custom CSS block that isn't in the design draft.

Read the current ids with GET /v1/template.

version_not_found

404

No published version of the design with that id on this help center.

Use an id from GET /v1/template/versions.

CHANGE_SETS_UNAVAILABLE (reason)

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 STAGING_UNAVAILABLE. Retrying doesn't help.

CHANGE_SET_FULL (reason)

409

The change set already holds 500 items. Items added before the limit stay added.

Put the rest in a second change set.

TOO_MANY_OPEN_CHANGE_SETS (reason)

409

The help center already has 25 open change sets.

Publish or delete one first.

CHANGE_SET_NOT_OPEN (reason)

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

POST /v1/articles/bulk

invalid_item

The item isn't an article object.

POST /v1/articles/bulk

validation_error

The item failed validation. error.errors lists the problems by field.

POST /v1/articles/bulk

invalid_argument

The item uses a language code your help center doesn't have.

POST /v1/articles/bulk

external_id_busy

Another request is importing the same external_id. Retry the item shortly.

POST /v1/articles/bulk

persist_failed

The article couldn't be saved.

POST /v1/images/bulk

storage_failed

The image couldn't be stored. Retry it.

POST /v1/images/bulk

import_failed

The image at the source_url was refused or couldn't be downloaded. The message says why.

Change set readiness.blockers[].reason

conflict, article_gone, category_gone

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 readiness.warnings[].reason

already_applied, locale_gap, staged_not_added

Worth a look, but publishing isn't blocked.

Change set rejected[].reason, when you add items

not_found

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.

error

Status

When

invalid_request

400, 401, 404

A public client sent no code_challenge, or an authorization code was used, expired or issued to another client (400). A refresh token that is invalid, expired or revoked (401). Dynamic client registration switched off (404).

invalid_client

401

Client authentication failed, the client isn't known, or the redirect_uri isn't one the client registered.

invalid_grant

400

The code_verifier doesn't match the PKCE challenge.

invalid_scope

Redirect

The authorization asked for a scope that doesn't exist.

unsupported_grant_type

400

A grant other than authorization_code or refresh_token.

access_denied

Redirect

The person clicked Deny on the consent screen.

invalid_target

400

A resource that isn't allowed, or that this grant didn't include.

invalid_redirect_uri

400

Client registration with missing or invalid redirect_uris.

invalid_client_metadata

400

Client registration with an unsupported grant_types or token_endpoint_auth_method.

temporarily_unavailable

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

401, JSON-RPC error -32600

No credential: sign in with OAuth, or send an API key as Authorization: Bearer.

401, error="invalid_token"

The OAuth token was rejected, for example because it expired or wasn't issued for https://mcp.helpcenter.io.

403, error="insufficient_scope"

The token lacks a permission the tool needs, such as analytics.read. Reconnect and approve it.

400, JSON-RPC error -32000

An MCP protocol version the server doesn't support.

406, 415

The request lacks Accept: application/json, text/event-stream or Content-Type: application/json.

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

404 {"error": "not_found"}

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.

429, JSON-RPC error 429

More than 120 requests a minute from your address, or 1,200 for the help center. Wait for Retry-After seconds.

413

A request body over 64 KiB.

-32700

The body isn't valid JSON.

-32600

A batch of messages (send one per request), or an unsupported MCP-Protocol-Version header.

-32601

An unknown method.

-32602

An unknown tool, or protocol parameters that don't match the request. Invalid tool arguments come back as a tool result with isError instead.

-32020, -32022

The MCP-Protocol-Version, Mcp-Method or Mcp-Name headers don't match the body, or name an unsupported protocol version.

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

WIDGET_NOT_FOUND

410

No widget has the app_id in your snippet.

SITE_AUTH_REQUIRED

403

The help center is private and the request had no valid JWT. See Sign readers into the widget with a JWT.

SITE_IP_RESTRICTED

403

The help center only opens from certain IP addresses, and this visitor's isn't one of them.

SITE_UNAVAILABLE

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 Accept: application/json, a typo in the path, or an unknown id on an article or category write.

Send the header, check the path and the id, and check the status before you parse.

A write answers {"status": "success", "message": "This is the public API of HelpCenter.io"}

Validation failed, and your client followed the 302 redirect to the API's welcome message.

Send Accept: application/json to get the error itself.

"The title field is required." although you sent a title

The request had no Content-Type: application/json, so the API didn't read the body.

Add the header.

401 with a key that looks right

A header other than apikey (api_key and X-API-Key aren't read), a deleted key, or a key whose creator's HelpCenter.io account was deleted.

Send apikey, or create a key.

401 Unknown API key. from /v1/change-sets

Change sets accept only API keys, not OAuth tokens.

Use an API key for change sets.

404 for an article you see in the dashboard

The key belongs to another help center, or the article is in the Trash.

Check GET /v1/sites, or restore the article.

422 "The include email field must be true or false."

true or false in a query string or form field.

Send 1 or 0.

429 although you send fewer than 300 requests a minute

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.

500 from POST /v1/articles

A title that is empty or only spaces in one of the languages you sent.

Send text in every title you include.

Was this article helpful?