# API keys

_Category: Authentication_

An API key lets your own code call the HelpCenter.io REST API for one help center. Send it in the `apikey` header of every request to `https://api.helpcenter.io/v1`. It keeps working until you delete it, or until the person who created it is no longer an Owner or Admin of the help center.

```
curl https://api.helpcenter.io/v1/sites \
  -H "apikey: $HELPCENTER_API_KEY" \
  -H "Accept: application/json"
```

The response names the help center the key belongs to, and only that one:

```
{
  "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",
      "published": true,
      "publicly_accessible": true,
      "languages": ["en"],
      "created_at": "2026-09-30 08:03:11"
    }
  ],
  "meta": {
    "items_count": 1
  }
}
```

## Create a key

**Who can do this:** Owners and Admins of the help center · **Plans:** All plans

In the dashboard, click the gear icon, then **Settings**. Under **API keys**, click **New key**, give the key a **Name** that says where it will be used, pick a **Scope**, and click **Create key**. Four settings below the scope are off unless you tick them. Leave **Team notes** off unless your integration works with your team's notes, **Design** off unless it reads or changes your help center's design, and **Custom domain** off unless it sets up your domain. **Redirects** lets the key's redirects lead to other sites; leave it off unless the integration needs that. You can change them later with **Edit access**; the last two only on a Read & write key. The steps are in [Create an API key](https://self.helpcenter.io/content/create-an-api-key).

![Screen recording. Under API keys, click New key. Name it after where it will be used, and pick a Scope. Click Create key, then Copy it somewhere safe.](https://helpcenter-io.s3.amazonaws.com/uploads/self/Ixbsnu8aauTTHSm1dLEwsos9dgUrKZI9INdmuhXH.gif)
Create the key in Settings, then copy it.

A key is a 64-character string, and Settings shows it only once, right after you create it. Click **Copy** then and store it where your code reads its secrets, such as an environment variable or a secret manager. Afterwards Settings shows only its last four characters, and nobody can read the full key again: if you lose it, create a new key and delete the old one. The samples on this site read the key from `HELPCENTER_API_KEY`.

## What a key can do

The scope you pick decides which endpoints the key can call. A key holds these [scopes](https://developers.helpcenter.io/content/scopes):

| Option | Scopes | What the key can do |
| --- | --- | --- |
| **Read only** | `content.read`, `analytics.read` | Read articles, categories, images, comments, staged changes and versions, change sets, interface translations and exports, and read analytics. |
| **Read & write** | `content.read`, `content.write`, `analytics.read`, `webhooks.manage`, `embed.manage` | Everything a Read only key can do, plus create, update and delete content, upload images, moderate comments, publish staged changes and change sets, manage webhooks, and change which websites may embed the help center and run its widget. |
| **Team notes** (optional, off by default) | `notes.read`, plus `notes.write` on a **Read & write** key | Read the private notes your team leaves on articles. With **Read & write**, also start notes on passages, reply to them and resolve them. |
| **Design** (optional, off by default) | `design.read`, plus `design.write` on a **Read & write** key | Tick **Allow access to the help center design** to let the key read the help center's design. With **Read & write**, it can also change the design and publish it, custom CSS and scripts included. See [Design API](https://developers.helpcenter.io/content/design-api). |
| **Custom domain** (optional, off by default) | `domain.manage`, on a **Read & write** key only | Tick **Allow managing the custom domain** to let the key connect, check and remove the help center's custom domain. Grant it only to tools that set up your domain. See [Custom domain](https://developers.helpcenter.io/content/custom-domain-api). |
| **Redirects** (optional, off by default) | No scope, on a **Read & write** key only | Tick **Allow redirects to other sites** to let the key's redirects lead to addresses outside the help center. Without it, they lead only to pages of this help center. See [Redirects](https://developers.helpcenter.io/content/redirects-api). |

A request outside the key's scopes fails with `403 Forbidden`, and the message names the scope the key lacks:

```
{
  "status": "error",
  "code": "insufficient_scope",
  "message": "This action requires the content.write scope."
}
```

You choose the scope when you create a key, and it can't change. The four settings can: any Owner or Admin can click **Edit access** on a key in Settings and change them. The key keeps its value, and the change applies from its next request. Team notes requests also need the person who created the key to have access to team notes in the help center, as an Owner, Admin, Editor or Translator; without it, they fail with `403` and the code `notes_forbidden`. Design requests need that person to be an Owner, Admin or Editor; otherwise they fail with `403` and the code `design_forbidden`.

## One key, one help center

A key always acts on the help center it was created in. OAuth apps pick a help center with the `X-HCio-Site` header or the `site` query parameter; with an API key, both are ignored. To work with several help centers, create a key in each one, or use [OAuth](https://developers.helpcenter.io/content/oauth).

## Send the key

- Send the key in the `apikey` header. The header name is not case-sensitive. The API reads the key from no other header or query parameter.
- Also send `Accept: application/json`, as the examples do. The API answers errors in JSON either way (see [Requests and responses](https://developers.helpcenter.io/content/requests-responses-and-conventions)).
- If a request carries both an `apikey` header and an `Authorization: Bearer` token, only the key is read. A valid token doesn't rescue an invalid key: the request fails.
- Each key has its own rate limit budget. See [Rate limits](https://developers.helpcenter.io/content/rate-limits).

A missing, mistyped or deleted key gets `401 Unauthorized` with this body. So does a key whose creator's HelpCenter.io account was deleted, whose creator is no longer an Owner or Admin of the help center, or that expired (only an AI agent's key expires, see below).

```
{
  "status": "unauthorized",
  "docs_url": "https://developers.helpcenter.io/content/quickstart-your-first-request",
  "create_url": "https://api.helpcenter.io/v1/agent/help-centers",
  "agent_docs_url": "https://developers.helpcenter.io/content/migrate-a-knowledge-base-as-an-ai-agent"
}
```

Branch on `status`. The other fields are links for whoever reads the response (`create_url` and `agent_docs_url` are for AI agents without an account), and the body never says which of these causes it was.

## Check a key

This program reports which help center a key reaches and whether it can write. `GET /v1/account` returns `kb.capabilities`, with `read` and `write` set for the credential that calls it.

```
// Check which help center an API key reaches and whether it can write.
// Node.js 18 or later, no dependencies. Run: node check-key.js
const API = 'https://api.helpcenter.io/v1';

async function get(path) {
  const res = await fetch(API + path, {
    headers: { apikey: process.env.HELPCENTER_API_KEY, Accept: 'application/json' },
  });
  const text = await res.text();
  if (!res.ok) throw new Error(`GET ${path} failed with ${res.status}: ${text}`);
  return JSON.parse(text);
}

async function main() {
  const { sites } = await get('/sites');
  const { kb } = await get('/account');
  console.log(`This key works with help center ${sites[0].id} (${sites[0].url}).`);
  console.log(`Read: ${kb.capabilities.read ? 'yes' : 'no'}. Write: ${kb.capabilities.write ? 'yes' : 'no'}.`);
}

main().catch((err) => {
  console.error(err.message);
  process.exit(1);
});
```

```
# Check which help center an API key reaches and whether it can write.
# Python 3.8 or later, standard library only. Run: python3 check_key.py
import json
import os
import sys
import urllib.error
import urllib.request

API = "https://api.helpcenter.io/v1"

def get(path):
    request = urllib.request.Request(
        API + path,
        headers={"apikey": os.environ["HELPCENTER_API_KEY"], "Accept": "application/json"},
    )
    try:
        with urllib.request.urlopen(request) as response:
            return json.load(response)
    except urllib.error.HTTPError as err:
        sys.exit(f"GET {path} failed with {err.code}: {err.read().decode()}")

sites = get("/sites")["sites"]
capabilities = get("/account")["kb"]["capabilities"]
print(f"This key works with help center {sites[0]['id']} ({sites[0]['url']}).")
print(
    f"Read: {'yes' if capabilities['read'] else 'no'}. "
    f"Write: {'yes' if capabilities['write'] else 'no'}."
)
```

```
<?php
// Check which help center an API key reaches and whether it can write.
// PHP 8 or later with the curl extension. Run: php check-key.php

const API = 'https://api.helpcenter.io/v1';

function get(string $path): array
{
    $ch = curl_init(API . $path);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'apikey: ' . getenv('HELPCENTER_API_KEY'),
        'Accept: application/json',
    ]);
    $raw = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    if ($raw === false || $status >= 400) {
        fwrite(STDERR, "GET $path failed with $status: $raw\n");
        exit(1);
    }

    return json_decode($raw, true);
}

$sites = get('/sites')['sites'];
$capabilities = get('/account')['kb']['capabilities'];
echo "This key works with help center {$sites[0]['id']} ({$sites[0]['url']}).\n";
echo 'Read: ' . ($capabilities['read'] ? 'yes' : 'no')
    . '. Write: ' . ($capabilities['write'] ? 'yes' : 'no') . ".\n";
```

An AI agent's key gets `403` with the code `agent_not_allowed` from `GET /v1/account`, whether or not its help center has been claimed, so the program stops there for that key.

With a **Read & write** key, it prints:

```
This key works with help center 1234 (https://acme.helpcenter.io).
Read: yes. Write: yes.
```

## Manage your keys

- **Every Owner and Admin sees every key.** Settings lists all of the help center's keys, whoever created them, masked to their last four characters, each with its scope, a tag for each setting it has (**Team notes**, **Design**, **Custom domain**, **External redirects**), when it was last used, and who created it. Any Owner or Admin can delete any of them, or change a key's settings with **Edit access**.
- **A key is shown once.** You can copy the full key only when you create it. To replace a key you lost, create a new one.
- **Deleting is immediate.** Click the trash icon next to the key, then **Delete key**. The next request with that key gets `401`.
- **Keys you create don't expire, and there is no rotate button.** To replace a key, create a new one, switch your integration to it, then delete the old one.
- **When someone leaves your team.** A key stops working as soon as the person who created it is removed from the team or moved to a role other than Owner or Admin: its next request gets `401`, and Settings tags it **Not working**. Before you remove someone or change their role, give each integration that uses one of their keys a new key of its own, then delete theirs.
- **Keys go with their help center.** When a help center is deleted, its keys stop working.

## Keys that AI agents hold

An AI agent that builds a help center for someone without an account gets a key of its own, named **AI agent**: a **Read & write** key with **Design** and **Custom domain** access, which expires with its help center (see [Migrate a knowledge base as an AI agent](https://developers.helpcenter.io/content/migrate-a-knowledge-base-as-an-ai-agent)). Until the help center is claimed, it reaches only that help center's content and design, and it can't change the design's scripts. It can never be allowed to redirect to other sites.

When you claim such a help center, the claim page asks whether the agent may finish setting it up. If you leave **Let this AI agent finish setting up your help center** ticked, the key becomes yours: Settings lists it as **AI agent (setup)**, created by you, with the date it expires, 30 days after the claim. It keeps working on your help center's content and design and, once a plan is active, its redirects and custom domain; any other request with it answers `403` with `agent_not_allowed`. To take the custom domain away from it, click **Edit access** on the key and untick **Allow managing the custom domain**. After the expiry date it is tagged **Expired** and its requests are refused. If you untick it, the key is deleted when you claim. When you publish the help center for the first time, you are asked again: tick **Let the AI agent keep editing it once it's live** to keep the key, or leave it unticked and the key is deleted as the help center goes live. While the key works, every page of the dashboard says that an AI agent can still edit the help center, with **Revoke access** for Owners and Admins. To end the agent's access sooner, delete the key.

## Keep keys secret

- Use keys on servers, in scheduled jobs and in CI. Never put a key in browser JavaScript, a mobile app or a code repository: anyone who can load the page, install the app or read the repository can read the key.
- The widget never needs an API key, and neither does single sign-on: signing readers in uses a JWT (see [Sign readers into the widget with a JWT](https://developers.helpcenter.io/content/widget-jwt) and [How JWT single sign-on works](https://developers.helpcenter.io/content/how-jwt-sso-works)).
- Create one key per integration, so that you can delete one without breaking the others, and pick **Read only** unless the integration writes.
- If a key leaks, create a replacement, switch to it, and delete the leaked key.
- A **Read & write** key with **Design** access can change the custom CSS and scripts that every visitor's browser runs once the design is published. Treat it like an admin password, and delete it when the work is done.

## What a key can't do

- **Reach another help center.** Each key works with the help center it was created in, and nothing else.
- **Act for other people.** If you're building an app that other HelpCenter.io customers connect to their help centers, use [OAuth](https://developers.helpcenter.io/content/oauth): each person approves your app and picks their help centers.
- **Narrow itself further.** A key you create has no expiry date, no IP address restriction and no per-endpoint permissions beyond its scope.
- **Use the MCP server on every plan.** Through the [HelpCenter.io MCP server](https://developers.helpcenter.io/content/the-mcp-connector-for-ai-agents) for AI agents, a key works only for a help center on the Growth or Catalyst plan. For a help center on another plan, every tool call is refused with a message that the MCP server is available on the Growth and Catalyst plans. Called directly, the REST API works with a key on every plan.

## API key or OAuth

|  | API key | OAuth access token |
| --- | --- | --- |
| Made for | Your own scripts and servers | Apps that act for a HelpCenter.io user, including AI assistants |
| Help centers | The one it was created in | The ones the person shared with your app |
| Header | `apikey: <key>` | `Authorization: Bearer <token>` |
| Permissions | **Read only** or **Read & write**, plus **Team notes**, **Design**, **Custom domain** and **Redirects** | The scopes your app requested and the person approved |
| Lifetime | Until you delete it, or its creator is no longer an Owner or Admin | 1 hour. The refresh token lasts 30 days, and each refresh returns a new one. |
| Revoked by | Deleting the key | The person, in **Connected Apps** |

## Related

- [Scopes](https://developers.helpcenter.io/content/scopes)
- [OAuth 2.1 for apps and AI clients](https://developers.helpcenter.io/content/oauth)
- [Quickstart: your first API request](https://developers.helpcenter.io/content/quickstart-your-first-request)
- [Rate limits](https://developers.helpcenter.io/content/rate-limits)
