# API Tokens

> Machine credentials for CorePanel's JSON-RPC API: creating a token, the scopes it carries, the methods it can reach and how it behaves when a licence lapses.

Source: https://www.corepanel.net/docs/api-tokens/
Last updated: 2026-08-11
Part of the CorePanel documentation — https://www.corepanel.net/docs

---

An **API token** is a credential a program holds. It lets a WHMCS install, a provisioning
script or a monitoring probe call CorePanel's JSON-RPC API without a browser and without
anybody's password.

Until tokens existed the API authenticated exactly one thing — an administrator's session
cookie — so every automation story ended at the same wall. A token is the way past it.

Issuing tokens requires **CorePanel Pro or Business**. Listing and revoking them work on
every edition.

## Where the tokens live

**Server → API tokens** lists every token this organisation has, revoked ones included.

![The API tokens page: a table with a row per token showing its name with the public prefix beneath it, the scopes as coloured chips, when it was last used and from which address, its expiry and an Active or Revoked status. A "Create token" button sits in the header. The last row is a revoked token, greyed out and without a revoke action.](https://www.corepanel.net/_astro/api-tokens-page.vHHBvNMM.svg)

The page is available on every edition, and only super-administrators reach it.

## Creating a token

From the panel, **Create token** asks for three things: a name, the scopes, and an
optional expiry. Scopes are fixed for the life of the token, so this dialog is the only
moment you get to choose them.

The credential appears once, immediately after:

![The reveal dialog over the API tokens page: a heading reading "Copy this token now", the full credential in a monospace field with a copy button, the token's prefix and scopes below it, a warning that it is sent as a bearer token and acts on the whole server, and a single "I have copied it" button.](https://www.corepanel.net/_astro/api-token-created.QsQdq2Ur.svg)

That dialog closes on the button and on nothing else — no click outside, no Escape. It is
the only appearance of the credential in its readable form, and the only remedy for
losing it is revoking the token and creating another.

From the command line:

```bash
corepanel api-token create "WHMCS production" --scope provisioning
```

```
Token "WHMCS production" created.

  cpk_7f3k9m2xq4bt_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2

This is the only time it will be shown. Copy it now.

  id:      1
  prefix:  7f3k9m2xq4bt
  scopes:  provisioning
  expires: never
```

> **The token is shown once**
>
> Only a hash of the token is stored. Nothing can print it again — not the panel, not the
> CLI, not the database. If you lose it, revoke it and create another.
The part before the last underscore (`cpk_7f3k9m2xq4bt`) is the **prefix**. It is public:
it identifies the token in the panel, in the CLI and in log lines, and it is safe to
quote in a support conversation. The rest is the secret.

## Using a token

Send it as a bearer token to the panel's JSON-RPC endpoint:

```bash
curl https://panel.example.com/api/rpc \
  -H "Authorization: Bearer cpk_7f3k9m2xq4bt_a1b2c3d4…" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"cp.ListAccounts","params":{}}'
```

A request that carries both a bearer token and a session cookie is treated as the
**token's** — which matters if you are testing an integration from a browser tab where
you are also signed in. The request does what it says it does, bounded by the token's
scopes rather than by your own session.

## Scopes

Every token carries one or more scopes, chosen when it is created and fixed for its
lifetime. To change them, create a new token and revoke the old one.

| Scope | What it allows |
|---|---|
| `readonly` | List and read. Safe for monitoring. |
| `provisioning` | Create accounts, suspend and unsuspend them, change packages and passwords. Implies `readonly`. |
| `terminate` | Delete accounts. |

`terminate` is separate on purpose, and it is worth using that way: a hosting provider can let
dunning run unattended with a `provisioning` token while cancellations stay behind a
human, by simply not granting the scope that destroys data.

## What a token can reach

A token reaches a **fixed list of methods** and nothing else. The list is not "everything
an administrator can do minus the dangerous parts" — it is an explicit set, and anything
absent is refused whatever scopes the token holds.

| Method | Scope required |
|---|---|
| `cp.getCorePanelDetails` | `readonly` |
| `cp.GetCoreCapabilities` | `readonly` |
| `cp.ListAccounts` | `readonly` |
| `cp.GetAccount` | `readonly` |
| `cp.ListAccountDomains` | `readonly` |
| `cp.ListPackages` | `readonly` |
| `cp.CreateAccount` | `provisioning` |
| `cp.UpdateAccount` | `provisioning` |
| `cp.UpdateAccountPassword` | `provisioning` |
| `cp.SuspendAccount` | `provisioning` |
| `cp.UnsuspendAccount` | `provisioning` |
| `cp.CreateAccountSession` | `provisioning` |
| `cp.DeleteAccount` | `terminate` |

The host firewall, the WAF, the MySQL console, backups, application environment variables
and token management itself are **unreachable by any token**. A token cannot create
another token, which is what stops a leaked credential from minting itself a wider one.

> **This list is a promise**
>
> These methods and their parameters carry a compatibility guarantee: new optional
> parameters and new response fields may appear, but nothing here will be renamed, removed
> or re-typed without a deprecation window. Methods outside the list are internal to the
> panel and may change at any time.
## Signing a customer in from your billing system

`cp.CreateAccountSession` is the one method that does not read or write an account — it
produces a **link that signs the customer in**. It is what a "Log in to your hosting"
button in a billing area calls: your system asks for a link, then redirects the browser
to it, and the customer arrives in their panel having typed nothing.

```json
{"jsonrpc":"2.0","id":1,"method":"cp.CreateAccountSession",
 "params":{"user":"acmecorp","client_ip":"203.0.113.44"}}
```

`user` is the account — its username, any domain it owns, or its id. `client_ip` is
optional: give it the address of the browser you are about to redirect and the link will
only work from there. It must be an IP address — anything else is refused rather than
ignored, so a link you asked to be bound is never quietly minted unbound. The answer
carries the URL to send them to:

```json
{"url":"https://panel.example.com/login?ott=…","token":"…",
 "user":"acmecorp","account_id":42,"expires_at":"2026-08-10T18:04:00Z"}
```

Three properties are worth knowing, because they decide how you use it:

- **It lasts sixty seconds.** It has to survive a redirect and nothing else. Ask for the
  link at the moment the customer clicks, never in advance.
- **It works exactly once.** The server destroys it the first time it is presented, so
  the copy left in a browser history or a `Referer` header opens nothing. The panel also
  strips it from the address bar before using it.
- **It is checked again on arrival.** An account suspended in the seconds between minting
  and use cannot get in — the link does not outlive the authorisation behind it.

If the server has no hostname recorded, `url` comes back empty and you build your own
from `token`; the path is `/login?ott=<token>` on the panel's address.

> **This signs somebody in — treat it like a password**
>
> A token with `provisioning` can produce a working session on any account on the server.
> Only call it for a customer your own system has already authenticated, and only over
> HTTPS. It is refused to a `readonly` token, and to every signed-in human: an
> administrator who needs to see a customer's panel uses **Log in as customer** in the
> panel instead, which is recorded against their name and shows a banner while it lasts.
## Who a token belongs to

A token belongs to the **seller** — the hosting organisation — not to the administrator
who created it. Removing that administrator does not affect it. This is deliberate: an
integration that provisions a hosting provider's customers has to outlive whoever set it up.

In the audit trail a token names itself rather than a person. An account suspended by a
billing run is recorded as `token 7f3k9m2xq4bt (seller #1)`, never as the administrator
who created the credential — so an operator reading the log can tell an automated action
from a human one.

## Expiry and revocation

Tokens do not expire unless you say so. That is the right default for a billing
integration: the alternative is provisioning that breaks on a date nobody wrote down.

```bash
# Expires at the end of 2026-12-31
corepanel api-token create "status page" --scope readonly --expires 2026-12-31
```

Revoking takes effect immediately, from the panel (the **Revoke** action on the row) or
from the command line:

```bash
corepanel api-token list
corepanel api-token revoke 4
```

The row is kept rather than deleted, so a token named in an old log line can still be
resolved to what it was. Revoking a token that is already revoked succeeds — the command
means "make sure this cannot be used".

## What happens when a licence lapses

An expired card, a network partition, a grace window that ran out — a server can drop to
Personal while integrations are running against it. CorePanel is deliberate about what
that does:

- **Existing tokens keep authenticating.** A licence that lapses at 3 a.m. does not stop
  a hosting provider's provisioning.
- **Issuing a new token is refused** until the licence is restored.
- **Revoking always works**, on every edition.
- **Individual operations degrade on their own terms.** Suspension, for instance,
  requires Business — so a lapsed server keeps answering `cp.ListAccounts` and refuses
  `cp.SuspendAccount` with a clear reason, rather than failing everything at once.

The effect is that usage reporting and reads keep working while the paid operations stop,
with an error an operator can read in their integration's activity log.

## Security notes

- **Treat a token like a password.** It is not scoped to one account: within its method
  list it acts on the whole server.
- **Use one token per integration.** They are free to create, and revoking one then costs
  you exactly that integration instead of all of them.
- **Grant the narrowest scope that works.** A monitoring probe needs `readonly`; almost
  nothing needs `terminate`.
- **Rotate by creating first.** Create the replacement, move the integration over, then
  revoke the old token — a token's scopes cannot be edited in place.

## Related

- [CorePanel CLI](https://www.corepanel.net/docs/cli) — the `corepanel api-token` command reference
- [Suspending an Account](https://www.corepanel.net/docs/accounts/suspension) — what `cp.SuspendAccount` actually does
- [Licensing](https://www.corepanel.net/docs/licensing) — editions and what happens when a licence lapses
