Personal API tokens
On this page
A personal API token lets you call the management API as yourself, from a script, without a browser session. It is the second way to authenticate a human - not a new kind of principal.
That distinction decides everything else on this page. A token resolves to the person who issued it, and authorization is answered from that person’s live role bindings on every request. Nothing about what you may do is stored on the token, so a revoked membership or a narrowed role takes effect on the next call without anyone having to remember the token exists.
Present it in the X-Polyant-Key header - the same header a management API key uses. The prefix tells them apart: pat_ is a person, pk_ is a service. One header means an API client has one thing to configure.
It is not a replacement for a management API key
The two live side by side on purpose, and picking the wrong one is the mistake this page exists to prevent.
Management API key (pk_) | Personal API token (pat_) | |
|---|---|---|
| Who it is | a service principal, scoped to one organization | the person who issued it |
| Where permissions come from | an explicit set stored on the key | that person’s live role bindings |
| Who administers it | an Owner, in Settings → Organization | the holder, on their own Account page |
| Survives its creator leaving | yes | no - it dies with their access |
| Reaches a second organization | never | wherever its holder is a member |
Automations belong on a management key. A pipeline authenticated as an employee either breaks on the day they are offboarded, or - worse - keeps running under an account that should have been closed. A personal token is for your scripts: the ones you run, on your own authority, and that should stop working when you do.
Creating one
The Personal API tokens section is on your Account page, at /account. Any signed-in user reaches it; there is no permission on it, because it only ever shows and manages your own.
Creating a token takes:
- Name - required, up to 255 characters. It is a label for you, so name it after the thing that will hold it (
laptop / curl,nightly export). - Expiry - 30, 90 or 365 days, or no expiry. The default is 90 days. An expired token is refused exactly as if it had never existed, and the list marks it Expired rather than letting it look healthy.
- Restriction - off by default. Leave it off and the token does whatever you can do, following your permissions as they change. Turn it on to pick a subset.
- Act as Platform Admin - shown only to a Platform Admin, off by default. See Acting as a Platform Admin.
The plaintext is returned once, at creation, as pat_<id>_<secret>. Only a hash is stored, and there is no way to see it again - copy it then. The <id> half is public and selects the row; the secret half is 32 random bytes.
One account holds at most 25 tokens. That is a guardrail against forgetting, not a quota: a list whose end you cannot see cannot answer the only question that matters after a leak, which is which of these do I still need.
Restricting a token
A restriction is a ceiling, never a grant. It is intersected with your live bindings on every request, so it can only ever subtract:
- Picking a permission you do hold narrows the token to it.
- Picking one you do not hold gives you nothing. The bindings still decide.
- Losing a binding later closes the token for that permission at once, restriction or not.
Restrict a token whenever the thing holding it needs less than you do - a read-only export script, a dashboard poller. Then a leak of that script is not a leak of your account.
A restricted token is also refused outright on routes that carry no permission to intersect against, such as
GET /api/me/access. If a script needs those, leave the token unrestricted and scope it by what it does instead.
What a token can never do
Three refusals hold regardless of who you are, and each closes a way a leaked token could outlive its own revocation.
It cannot mint or replace a credential. Changing your password, enrolling or removing your second factor, accepting an invitation, and creating or revoking tokens themselves all require an interactive session. A token that can issue its successor survives being revoked; and setting a password is the sharpest case - on an account that has none (a Google-only sign-in), doing so mints a full session that revoking the token does not touch.
The same holds for the platform routes whose effect outlives the token: impersonation, and creating a user, editing a user and resetting a user’s password (POST /api/users, PATCH /api/users/{id}, POST /api/users/{id}/reset-password). They refuse every personal token, one minted with platform standing included. A new account, a promotion to Platform Admin or a reset password would turn a leaked token into permanent access, and impersonation changes what the admin’s own browser session acts as.
With managed services the same holds for the writes that outlive a token by changing the installation or the organization itself. These accept only a signed-in session, and refuse a personal token and a management key alike:
- the outbound mail relay,
PUTandDELETE /api/platform/mail; - the installation settings,
PATCH /api/platform/settings, whose public addresses are the origin of every password-reset, invitation, webhook and OAuth link; - deleting an organization,
DELETE /api/organizations/{orgSlug}; - deleting a custom role,
DELETE /api/organizations/{orgSlug}/roles/{roleId}. A custom role that someone still holds answers409instead of being deleted.
By default it does not act as a platform admin. A token carries its holder’s role bindings, and their platform-admin standing only when it was minted with it, which is an explicit choice described below. Platform-admin standing bypasses authorization on every permission-checked route, so a token that inherited it silently would be a bearer credential for the whole deployment.
So a default token is refused on the platform-admin routes, and everywhere else it is authorized on its holder’s bindings as if they held no special standing.
One route is the exception, with managed services: creating an organization (POST /api/organizations) accepts a personal token whose holder is a platform admin at the moment of the call, checked against the database rather than a cache. The token’s restriction still applies, so a restricted token needs org:write in its ceiling. A management API key is refused there, because it has no person whose standing could be checked. A platform admin usually belongs to no organization, and therefore holds no bindings - so their own token is refused nearly everywhere. That is the correct answer rather than an inconvenience: automation at that level is what a management API key is for.
It cannot skip a two-factor mandate. If your organization requires a second factor and you have not enrolled, your tokens are refused just as your browser is. A token issued before the mandate was switched on does not keep working around it.
Acting as a Platform Admin
Managed services.
A Platform Admin whose script must act across organizations or on the Admin Console routes can tick Act as Platform Admin when creating the token (POST /api/me/tokens with platformStanding: true). The option is shown and accepted only for someone who is a Platform Admin at that moment; anyone else is refused with 403. Such a token:
- must expire within 24 hours of its creation, the maximum age of a browser session. The panel offers 1, 8 or 24 hours, with 1 hour as the default; the API refuses a longer or missing expiry with
400; - is re-checked live: it acts as a Platform Admin only while its holder still is one, so a demotion ends its standing without revoking the token;
- keeps its restriction: a restricted token with standing still can do only what its restriction allows, and is refused on the Admin Console routes, which carry no permission to intersect;
- is marked with a Platform Admin badge in the token list;
- is audited at creation and on every request its standing lets through (see Auditing).
It still cannot do what no token can: impersonation, the credential routes, creating, editing or resetting the password of users, the outbound mail relay and the installation settings stay session-only. Listing, reading and deleting users and revoking their tokens are open to it. A dev token or an invitation it issues expires no later than it, dies with it on revoke, and is re-checked against the same standing.
Every token created before this option existed, and every token created without it, behaves as described in the rest of this page.
Revoking
Revoke from the same section on your Account page. The token is checked against the database on every request with no caching, so revocation takes effect on the very next call - there is no window to wait out, and no way for the token to renew itself.
Managed services. With managed services, revoking a token also ends what it issued. A dev token created through a personal token is deleted with it, which closes a live dev session at its next keepalive check, and an invitation it created is revoked if still pending. Neither can outlive the token’s own expiry either. Dev tokens and invitations issued before the upgrade to 1.2.0 carry no link to a token and are not affected.
Deleting the account, or removing the person from every organization, has the same effect for a different reason: the token’s authority is their bindings, so with none left it can do nothing. This holds for a platform admin’s default token too. A token minted with platform standing keeps it only while its holder is still a Platform Admin.
Auditing
Creating and revoking a token each write a management-audit row (user.api_token_create, user.api_token_revoke) naming the actor and which token - see Audit logs. A token with platform standing is audited as user.api_token_create_platform_standing at creation, and every request its standing lets through writes user.api_token_platform_standing_use with the token id, method, route and the permission or lane it passed. The secret is never audited, never logged, and never recoverable.
last_used_at on the list is best-effort observability, refreshed at most once a minute. It answers “is anything still using this”, not “when exactly was the last call”.
Using one
export POLYANT_KEY=pat_...
curl -H "X-Polyant-Key: $POLYANT_KEY" https://your-host/api/instancespolyant-cli and the dev kit (v1.2.0) read the same header, so a token works wherever they do - subject to the refusals above.
It is not a drop-in for a management key even so, and not only because of those refusals: GET /api/service-context accepts a management key and nothing else, because its job is to report a key’s own scope and gating self-introspection on anything grantable would fail for exactly the narrowly scoped key that most needs to read it. Ask GET /api/me instead - that is the human equivalent. The REST API reference states which credentials each route accepts.