Personal tokens
A personal token opens the dashboard API to a script on your behalf: with your role in each project, in the areas and projects you pick, from allowed addresses.
A personal token is your key to the dashboard API, the same API the interface runs on. A script with the token acts on your behalf: it reads and changes what you see and change in the dashboard, in all your projects. A token never adds to your rights, it only narrows them: your role in each project is checked as in the dashboard, and on top of it come the token’s areas, projects, expiry and allowed addresses.
Project key or personal token
| What | Project key | Personal token |
|---|---|---|
| Format | sk_… | shp_… |
| Belongs to | The project | You |
| Opens | The public API /api/v1 | The dashboard API /api/… |
| Projects | One, its own | All of yours, or the ones you pick |
| Rights | The project’s whole public API | Your role, narrowed by areas |
| Who issues it | The project owner and admins | Any member, for themselves |
| Where | Settings → API Keys | Profile → Personal API |
| Expiry and addresses | None | 30, 90 or 365 days or no expiry, up to 20 addresses |
The public API, /api/v1, takes only a project key, and the dashboard API takes only a sign-in: a session or a personal token. Connect the project’s integrations (a CRM, a bot, a channel of your own) with a key: it doesn’t depend on people and their roles. A personal token is for scripts that do what you do in the dashboard: exports, reports, bulk edits, work across several projects at once.
Issuing a token
- Open Profile → Personal API and click Issue token. Every project member has this section: you issue tokens for yourself.
- Fill in the form:
- Name: 1 to 100 characters, to tell the token apart in the list and in the email.
- Valid for: 30 days, 90 days, 1 year or no expiry. 90 days by default.
- Access: Full access (read and write in every project area), Read only, or Pick by area.
- Projects: All my projects, Selected, or, for superusers only, All projects on the platform.
- Allowed addresses: optional, one per line.
- Current password: a token works without a 2FA code, so issuing one takes your password.
- Click Issue and copy the token right away. It’s shown in full only once: only its hash is stored, and the list shows its start, the first 8 characters (
shp_and four more).
- For every new token you get an email, “Personal API token created”, with the name, areas, projects, expiry, address and time of issue. The token itself isn’t in it. If you didn’t issue the token, revoke it and change your password. Changing the password in your profile doesn’t end tokens by itself.
- One person can have at most 25 tokens, expired ones included. Past that, issuing answers 400
PERSONAL_TOKEN_LIMITuntil you revoke one you don’t need. - A wrong password counts as a failed sign-in. After 10 failures in a row, each within 15 minutes of the one before, signing in and issuing tokens are closed until 15 minutes have passed since the last failure (429
AUTH_LOGIN_LOCKED), even with the right password. - Signing in with SSO and don’t know your password? Set one via “Forgot password”.
- Tokens are issued and revoked only in the dashboard: a token can’t issue or revoke tokens.
- In someone else’s account, entered by a platform admin from the admin area, issuing is closed. Viewing and revoking tokens works there.
- Enterprise Dedicated with the license in read-only mode: issuing is closed, revoking works. Tokens still read with GET and HEAD, while any POST, PUT, PATCH or DELETE answers 423.
First request
Pass the token in the Authorization header with the Bearer scheme:
Authorization: Bearer shp_xxxxxxxxxxxxxxxxxxxxxxxxThe dashboard API’s paths start with /api. Almost everything about a project lives under /api/w/{workspace_id}/…, where workspace_id is the project’s id. The list of your projects gives it: GET /api/workspaces/, with the trailing slash. It needs the Projects area with read. Without it the answer is 403 PERSONAL_TOKEN_SCOPE with {"area": "projects", "need": "read"} in detail:
curl https://supporthub.cc/api/workspaces/ \
-H "Authorization: Bearer shp_xxxxxxxxxxxx"{
"items": [
{ "id": "6f1c2a7e-…", "name": "Altair Cloud", "slug": "altair", "my_role": "admin", … }
],
"total": 1,
"page": 1,
"page_size": 20,
"pages": 1
}The list is paged: page and page_size (up to 100). A token limited to selected projects sees only those, and their ids are also in the answer of GET /api/personal-tokens/current, with no Projects area needed.
Then call the project’s routes, for example the list of tickets (the Tickets area with read):
curl https://supporthub.cc/api/w/<project_id>/tickets \
-H "Authorization: Bearer shp_xxxxxxxxxxxx"The answer is a page of tickets in the same shape: items, total, page, page_size, pages.
Three requests are open to any working token, with no areas:
GET /api/personal-tokens/current: the token itself, with its name, areas, projects, addresses, expiry, and when and where it was last usedGET /api/auth/me: whose token it isGET /api/openapi.json?scope=personal: the endpoints the token opens (addlang=enfor English)
A token that won’t do gets its own error from all three, in the common envelope: PERSONAL_TOKEN_INVALID for no such token or a revoked one, PERSONAL_TOKEN_EXPIRED for a term that’s over, PERSONAL_TOKEN_IP for an address that isn’t allowed.
{
"id": "5d1e0b44-…",
"name": "CRM export",
"token_prefix": "shp_Ab3x",
"scopes": { "tickets": "read", "contacts": "read" },
"projects_kind": "listed",
"workspace_ids": ["6f1c2a7e-…"],
"allowed_ips": ["203.0.113.0/24"],
"expires_at": "2027-02-04T09:30:00",
"last_used_at": "2026-11-06T09:41:12",
"last_used_ip": "203.0.113.7",
"created_at": "2026-11-06T09:30:00"
}Times in answers are UTC. The same reference, by area, with models and request examples, is in the API docs tab of Profile → Personal API.
Areas
An area is a part of the dashboard API, and its level is read or write. Write includes read. Reads are GET and HEAD requests, writes are the other methods. Three POST requests that change nothing count as reads: the project log export to CSV, the preview of a project email and the preview of a platform broadcast. Assistant suggestions and message translation, though, are writes: each call spends the project’s budget.
| Area | What it opens |
|---|---|
tickets Tickets | Tickets and messages, notes, assignment, inbox folders and views, reactions, conversation links and PDF statements, assistant suggestions in a ticket, BILLmanager data in a ticket, live visitors, bell notifications |
contacts Contacts | Contacts, a contact’s emails, the client card |
knowledge Knowledge base | Articles and categories, news and polls |
templates Reply templates | Templates, macros, quick replies, your own greeting and signature |
channels Channels | Channels, proxies, the widget and its builder, visitor notifications |
broadcasts Broadcasts | Broadcasts to contacts, sending news to every contact |
team Team | Operators, departments, project invitations |
ai AI assistant | Assistant settings, memory, connections, sources, the reply journal |
analytics Analytics | Reports, the project log, exports of tickets, contacts and conversations |
settings Project settings | Project settings and texts, project emails and their look, file storage |
billing Plan and billing | The project’s plan and its renewal, plan changes, promo codes, balance top-ups, payment history |
projects Projects | The list of your projects, a project by slug, creating a project, the order of projects in the list |
media Files | Uploading and downloading files |
profile Profile | Your status, your departments and email for notifications, your work hours |
admin Platform admin | The platform’s users and projects, moderation, payments, revenue and plans read-only, expense records, platform broadcasts and announcements, the changelog, proxies, platform health. Platform admins only |
The Platform admin area goes only to platform admins, and never to a token with selected projects: the admin area is one for the whole platform. A superuser gets it only together with All projects on the platform.
Projects
- All my projects: the projects you’re a member of, including ones you join later. Leave a project and the token stops working there.
- Selected: only the projects you tick, 1 to 1000 of them, and only ones you’re a member of. A request to another project answers 403
PERSONAL_TOKEN_PROJECT. - All projects on the platform: superusers only. Any project on the platform, as they have it in the dashboard.
A token doesn’t change your role in a project: an operator’s token with Full access still can’t change the project settings, just as in the dashboard. A superuser keeps superuser rights only with an All projects on the platform token. With All my projects or Selected, a superuser is an ordinary member of their projects for the request, with no access to other projects or to the admin area.
Routes with no project in the path, for example GET /api/tickets/{ticket_id}/locate or POST /api/announcements/{ann_id}/ack, take the project of the object they find and check it the same way.
Expiry and addresses
A token works until its term is over: 30, 90 or 365 days from issue, or no expiry. A term can’t be extended: issue a new token. An expired token answers 401 PERSONAL_TOKEN_EXPIRED and stays in the list, marked Expired, until you revoke it. Until then it counts toward the 25.
Allowed addresses are optional: an empty list means from anywhere. The list takes up to 20 IP addresses or networks, IPv4 and IPv6, for example 203.0.113.7, 203.0.113.0/24, 2001:db8::/32. A network is stored by its network address: 203.0.113.9/24 becomes 203.0.113.0/24. An address with the ::ffff: prefix is refused: write the IPv4 address as is. The address checked is the one the request reached us from: behind NAT or a proxy, their outside address. A request from any other address answers 403 PERSONAL_TOKEN_IP.
The list of tokens shows when and from which address each token was last used. The mark may lag by up to a minute.
Reading leaves no trace
A script reading conversations isn’t a person who read them. So requests with a token:
- don’t tell the client their message was read
- don’t mark the ticket as read for operators
- don’t count a knowledge base article view
- don’t start the background translation of older messages
- don’t show you as online to widget visitors
Writing with a token is ordinary writing. A reply goes to the client in their channel on your behalf, with every consequence of a reply from the dashboard. An explicit translation request translates the message, as the button in the dashboard does.
A token doesn’t open the operator WebSocket. For real-time events, use webhooks or long polling of the public API, with a project key.
Files
Clients’ files, meaning anything not attached to an internal note, are served by GET /api/media/{media_id} with no sign-in, as in the widget and in email. A file of an internal note goes only to a project member, and with a token only if the token also has the Files area with read.
Such a file never confirms it exists. Any refusal of the token answers 404 MEDIA_NOT_FOUND, as a missing file does: the term is over, the token is revoked, another address, no Files area, or a project outside the token’s list on the /api/w/{workspace_id}/media/… path. There is one exception: on GET /api/media/{media_id} a project member whose token leaves that project out gets 403 PERSONAL_TOKEN_PROJECT. Got a 404 for an internal file? Check the token first with GET /api/personal-tokens/current.
Signed file links in message lists, like …?exp=…&sig=…, open without a token. Treat them as secrets: anyone who gets such a link can open the file for 12 hours.
Always closed
Part of the API is closed to every token, whatever its areas. A token can’t create a new sign-in or keys, take secrets out, change sign-in data, its own or anyone else’s, grant platform roles or project ownership, handle the platform’s money, plans and licenses, or delete a project or an account. Closed are:
- token management: everything under
/api/personal-tokens, exceptGET /api/personal-tokens/current - sign-in, password, sessions, 2FA, passkeys and SSO: everything under
/api/auth, exceptGET /api/auth/me, and accepting an invitation - project keys
sk_… - secrets that would outlive the token: the signing secret for widget visitor tokens, a channel’s secret inbound email address, the BILLmanager module setup, the WhatsApp linking QR code, the project’s SSO
- a new operator with a password, handing over project ownership, deleting a project
- actions on clients’ BILLmanager services from a ticket, running macros, client card buttons
- the list of payment methods and payment provider webhooks, the license, device push subscriptions, an install’s first-run setup
- in the platform admin area: signing in as another user, editing and deleting users, their passwords, 2FA and passkeys, their roles in projects, a user’s emails in full, writing platform settings and system emails, support sessions, marking payments paid, a project’s balance and plan, editing plans, discounts and promo codes, issuing and renewing licenses, moving Telegram bots to another Bot API (it would hand every bot token on the platform to the address in the request)
- in Enterprise Dedicated: support access, starting an update, the install’s domain and SMTP
The answer is 403 PERSONAL_TOKEN_CLOSED with closed in detail.reason. If it says unmapped, the route is new and isn’t in the token map yet: until it is, it stays closed to tokens.
Strong actions still sit in the areas, open to a token with write there. Team changes members’ admin rights, deactivates them and removes them from the project. Plan and billing, for the project owner, renews and changes the plan from the project balance and issues invoices. Platform admin blocks people, removes them from projects and ends their sessions. Give write in these areas only to scripts that need it.
Errors
Token errors come in the common envelope: error_code, error_message, detail, request_id. The format and retries are on the Limits & errors page.
| Response | When |
|---|---|
400 PERSONAL_TOKEN_LIMIT | When issuing in the dashboard: you already have 25 tokens. detail: {"max": 25} |
401 PERSONAL_TOKEN_INVALID | No such token: a typo, a revoked token or a deleted account |
401 PERSONAL_TOKEN_EXPIRED | The token’s term is over |
403 PERSONAL_TOKEN_IP | The request came from an address that isn’t allowed |
403 PERSONAL_TOKEN_CLOSED | The route is closed to tokens. detail.reason: closed means always closed, unmapped means the route isn’t in the token map yet |
403 PERSONAL_TOKEN_SCOPE | The token lacks the route’s area or the level it needs. detail: {"area": "tickets", "need": "write"} |
403 PERSONAL_TOKEN_PROJECT | The request’s project isn’t one of the token’s projects |
404 PERSONAL_TOKEN_NOT_FOUND | In the dashboard: revoking a token that’s already gone. Also GET /api/personal-tokens/current with a plain session, no token |
404 MEDIA_NOT_FOUND | A file of an internal note, and the token won’t do for it (see Files) |
429 RATE_LIMIT_EXCEEDED | Too many requests. The Retry-After header says in how many seconds to retry |
The checks go in order: the token, its expiry, the address, the route, the area, the project. Then the route checks you yourself, as in the dashboard. A blocked account answers 403 with {"detail": "User account has been banned."}, and missing rights in a project give the same 403 and 404 as in the interface. With no Authorization header the answer is 401 with {"detail": "Could not validate credentials."}.
Rate limit
Each token has its own budget: 1,000 requests per minute across all routes. An API process remembers a token for 5 minutes after its last accepted request. The first request in each process, and the first one after such a pause, count toward the client address’s budget (also 1,000 per minute), like a request without a token. Over the limit you get 429 RATE_LIMIT_EXCEEDED with a Retry-After header.
Some routes have limits of their own, stricter than the common one, for example 20 to 30 requests per minute for assistant suggestions. They count by the client address, whatever header the request carries.
What ends a token
Revoke tokens in Profile → Personal API, one by one or all at once with Revoke all. A revoked token stops working at once and answers 401 PERSONAL_TOKEN_INVALID. It can’t be brought back, only replaced with a new one.
All your tokens are revoked by themselves when:
- your password is reset with the link from the email
- a platform admin clicks End all sessions, Set password or Reset link in your user card
- the account is deleted
Changing your password in the profile and End others in your sessions leave tokens working: tokens live apart from sessions. Blocking the account stops its tokens (403), and after unblocking they work again. A platform admin sees your tokens in your user card, only their start, and can revoke any of them.

