Every REST API request that reads or changes content acts on exactly one help center. An API key always acts on its own help center. An OAuth access token acts on the only help center it reaches, or on the one you name with the X-HCio-Site header.
curl "https://api.helpcenter.io/v1/articles?limit=1" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "X-HCio-Site: 1234" \
-H "Accept: application/json"
API keys: always their own help center
A key belongs to the help center it was created in, and every request with it acts on that help center. The API ignores X-HCio-Site and ?site= on requests made with a key, even when they name another help center. To work with several help centers, create a key in each one. See API keys.
OAuth tokens: the help centers a token reaches
A token reaches only help centers that meet both conditions:
The HelpCenter.io account of the person who approved your app owns them.
The person shared them with your app, on the consent screen or later in Connected Apps.
The API checks both on every request, so a change applies to your next request without a new token. When the person stops sharing a help center, requests that name it fail. When they share another one, it becomes reachable.
A help center that belongs to another account can't be shared this way, even when the person is on its team. The consent screen doesn't offer it. To work with it, use an API key created in that help center.
How the API picks the help center
The token reaches | You name no help center | You name one it reaches | You name anything else |
|---|---|---|---|
One help center | That help center | That help center |
|
Several help centers |
| The one you named |
|
No help center |
| Not possible |
|
If the token reaches one help center, you don't need to name it, but naming it does no harm and keeps your code working when the person shares a second one.
Name the help center
Send the help center's numeric id in the X-HCio-Site header, or in the site query parameter:
curl "https://api.helpcenter.io/v1/articles?limit=1&site=1234" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"
Use the numeric id. Other values, such as a subdomain or a domain, are refused with
403site_forbidden.The header wins. When a request carries both, the API uses
X-HCio-Siteand ignoressite.Two endpoints never need it.
GET /v1/sitesandGET /v1/accountdescribe the credential rather than one help center. They ignore the header and the parameter.
Find the ids
GET /v1/sites lists the help centers the token reaches, ordered by id. It needs the content.read scope.
curl https://api.helpcenter.io/v1/sites \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"
The response, for a token that reaches one help center:
{
"status": "success",
"sites": [
{
"id": 1234,
"uuid": "8f14e45f-ceea-4f6a-9d3b-2c5e1a7b9c01",
"name": "Acme Help",
"subdomain": "acme",
"domain": "acme.helpcenter.io",
"url": "https://acme.helpcenter.io",
"default_language": "en",
"visibility": "public",
"publicly_accessible": true,
"languages": ["en"],
"created_at": "2026-09-30 08:03:11"
}
],
"meta": {
"items_count": 1
}
}
GET /v1/account answers the same question in brief, in kb.sites_count and kb.site_ids, and needs no scope. Every field of both endpoints is in Sites and account.
Don't keep an id forever. Read GET /v1/sites when your app starts a sync, and again whenever a request fails with site_forbidden.
Errors
400 site_required
The token doesn't reach exactly one help center, and the request didn't name one. sites lists the ids you can name:
{
"status": "error",
"code": "site_required",
"message": "This account spans more than one help center. Name the one to act on with the X-HCio-Site header or the site query parameter.",
"sites": []
}
The message is the same when the token reaches no help center at all, as in this example, where sites is empty. That happens when the person's account owns no help center, for example someone who only works on another account's help center. Naming an id won't help: ask the person to connect your app with an account that owns the help center, or use an API key from that help center.
403 site_forbidden
The request named a help center the token doesn't reach:
{
"status": "error",
"code": "site_forbidden",
"message": "The requested site is not part of this account."
}
The answer is the same whether the id exists or not, so it tells you nothing about other people's help centers. The usual causes: the person never shared that help center with your app, stopped sharing it in Connected Apps, the help center belongs to another account, or the value isn't a numeric id. Read GET /v1/sites again rather than retrying.
AI assistants: add ?site= to the MCP server URL
AI assistants that connect through the HelpCenter.io MCP server choose the help center once, for the whole connection. When the person shares several help centers, add the id to the server URL when you add it to the assistant:
https://mcp.helpcenter.io/?site=1234
The MCP tools have no site argument. Without ?site=, and with more than one help center shared, the tools answer that the connection hasn't said which help center it's for. With exactly one shared help center, no ?site= is needed. A connection that uses an API key ignores ?site=, like the API does. See Connect an AI assistant to your help center.