API tokens
Create, list, edit, revoke, and regenerate the pa_live_ tokens that authenticate programmatic access.
API tokens are how integrations authenticate: long-lived bearer tokens
shaped pa_live_<random>, carried in the Authorization header of every
request. This page covers their full lifecycle; how to use one on other
routes is in Authentication.
Token routes are account-scoped — they need only Authorization, no
X-Workspace-Id. Tokens belong to you, not to a workspace; one token can
reach every workspace your account can.
The token value is returned once, at creation or regeneration — only its SHA-256 hash is stored, so it can never be shown again. Copy it immediately and keep it secret. Rotate a leaked or aging token with regenerate: same token ID, fresh secret, old one stops working immediately.
Create a token
| Field | Meaning |
|---|---|
name required | Display name, ≤ 100 characters. |
scopes optional | The token's permissions — omit for the defaults below. |
expires_in_days optional | 1–3650. Omit and the token never expires. |
curl -X POST "$API_BASE/v1/user/api-tokens" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "CI forecaster",
"scopes": ["models.read", "inferences.create"],
"expires_in_days": 180
}'from predictai import PredictAI
client = PredictAI(token="pa_live_…")
data = client.account.post_user_api_tokens(json={
"name": "CI forecaster",
"scopes": ["models.read", "inferences.create"],
"expires_in_days": 180,
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…" });
const data = await client.account.postUserApiTokens({
json: {
name: "CI forecaster",
scopes: ["models.read", "inferences.create"],
expires_in_days: 180,
},
});
{
"data": {
"token": "pa_live_Xk3vNpR7wYtL2mHc4sB9dEjaGuVi8fQz",
"token_id": "tok_4b9d2f81a6c35e07",
"name": "CI forecaster",
"scopes": ["models.read", "inferences.create"],
"last_used_at": null,
"created_at": "2026-07-15T12:04:33.120584+00:00",
"updated_at": "2026-07-15T12:04:33.120584+00:00",
"regenerated_at": null,
"expires_at": "2027-01-11T12:04:33.120584+00:00",
"is_active": true,
"status": "active",
"message": "Save this token securely - it won't be shown again"
}
}A 201 means the token is live. token is the secret to save;
token_id is the handle you'll use to edit, revoke, or regenerate it.
Creating a token also sends you a security notification via
Notifications.
Errors
| Status | Why | Example message |
|---|---|---|
400 | Missing or blank name | "Token name is required" |
400 | Name over 100 characters | "Token name must be at most 100 characters" |
400 | Empty or non-list scopes | "Scopes must be a non-empty list" |
400 | A scope isn't in the table below | "Invalid scopes: pipelines.write" |
400 | Expiry out of range or not an integer | "expires_in_days must be between 1 and 3650" |
429 | Too many tokens created in a short window | "Too many API tokens created. Please try again later." |
Scopes
Scopes are shaped <resource>.<action>; a request outside the token's
scopes fails with 403. Grant the minimum the integration needs:
| Area | Scope | Grants |
|---|---|---|
| Workspaces | workspaces.read | List and inspect workspaces |
workspaces.write | Create, edit, and manage workspaces | |
| Goals | goals.read | Read goals, runs, tournaments, forecasts |
goals.write | Create and manage goals | |
| Data | data.read | Read data sources and ingestion state |
data.write | Create sources, run ingestion jobs | |
signals.read | Read signals and their values | |
signals.write | Push and delete signal data | |
| Discovery | discovery.read | Read discovery runs and results |
discovery.write | Start discovery runs | |
| Modeling | segments.read | Read segments |
segments.write | Create and edit segments | |
models.read | Read models and pipelines | |
models.write | Create and edit models and pipelines | |
trainings.read | Read training runs and reports | |
trainings.run | Trigger training runs | |
| Serving | deployments.read | Read deployments and serving status |
deployments.write | Deploy, scale, and delete deployments | |
inferences.read | Read inference history | |
inferences.create | Run forecasts |
Defaults — a token created without scopes gets workspaces.read,
goals.read, models.read, inferences.create: enough to run forecasts,
nothing more.
Tokens from earlier releases may still carry the legacy scope
models.deploy (superseded by deployments.write) — it stays valid on
existing tokens and is accepted when editing, but new grants should use
the current names.
List your tokens
curl "$API_BASE/v1/user/api-tokens" \
-H "Authorization: Bearer $TOKEN"from predictai import PredictAI
client = PredictAI(token="pa_live_…")
data = client.account.get_user_api_tokens()
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…" });
const data = await client.account.getUserApiTokens();
{
"data": {
"tokens": [
{
"token_id": "tok_4b9d2f81a6c35e07",
"name": "CI forecaster",
"scopes": ["models.read", "inferences.create"],
"last_used_at": "2026-07-15T14:22:08.551903+00:00",
"created_at": "2026-07-15T12:04:33.120584+00:00",
"updated_at": "2026-07-15T12:04:33.120584+00:00",
"regenerated_at": null,
"expires_at": "2027-01-11T12:04:33.120584+00:00",
"is_active": true,
"status": "active"
}
],
"count": 1
}
}Newest first. Only metadata is returned — token values are stored hashed
and are never listed. Revoked tokens disappear from the list; tokens past
their expires_at still appear with "status": "expired" until they're
cleaned up. last_used_at updates on every authenticated request, so it's
the quickest way to spot a token nothing uses anymore.
Rename or edit scopes
PATCH changes a token in place — the secret is untouched, so nothing breaks for callers already using it. Send at least one of the two fields:
| Field | Meaning |
|---|---|
name optional | The new display name. |
scopes optional | Replaces the whole scope list. |
curl -X PATCH "$API_BASE/v1/user/api-tokens/$TOKEN_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "scopes": ["models.read", "trainings.read", "inferences.create"] }'from predictai import PredictAI
client = PredictAI(token="pa_live_…")
data = client.account.patch_user_api_tokens_by_token_id(TOKEN_ID, json={
"scopes": ["models.read", "trainings.read", "inferences.create"],
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…" });
const data = await client.account.patchUserApiTokensByTokenId(TOKEN_ID, {
json: {
scopes: ["models.read", "trainings.read", "inferences.create"],
},
});
{
"data": {
"token_id": "tok_4b9d2f81a6c35e07",
"name": "CI forecaster",
"scopes": ["models.read", "trainings.read", "inferences.create"],
"updated_at": "2026-07-16T09:12:45.008211+00:00",
"status": "active",
…
}
}Errors
| Status | Why | Example message |
|---|---|---|
400 | Body has neither name nor scopes | "Nothing to update - provide name and/or scopes" |
400 | Invalid name or scopes (same rules as create) | "Invalid scopes: pipelines.write" |
404 | token_id doesn't exist, isn't yours, or was revoked | "Token not found or revoked" |
Revoke a token
Revocation is immediate and permanent — the next request with that token
gets 401. There is no un-revoke; create a new token instead.
curl -X DELETE "$API_BASE/v1/user/api-tokens/$TOKEN_ID" \
-H "Authorization: Bearer $TOKEN"from predictai import PredictAI
client = PredictAI(token="pa_live_…")
data = client.account.delete_user_api_tokens_by_token_id(TOKEN_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…" });
const data = await client.account.deleteUserApiTokensByTokenId(TOKEN_ID);
{
"data": {
"message": "API token revoked successfully",
"token_id": "tok_4b9d2f81a6c35e07"
}
}A 404 ("Token not found or already revoked") means the ID doesn't
exist, isn't yours, or was already revoked.
Regenerate a token
Rotation without reconfiguration: the token keeps its token_id, name,
and scopes, but gets a fresh secret. The old value stops working the
moment this call returns, usage tracking resets, and — if the token had an
expiry — the clock restarts with the originally chosen lifetime.
| Field | Meaning |
|---|---|
name optional | A new display name to apply along with the rotation. |
curl -X POST "$API_BASE/v1/user/api-tokens/$TOKEN_ID/regenerate" \
-H "Authorization: Bearer $TOKEN"from predictai import PredictAI
client = PredictAI(token="pa_live_…")
data = client.account.post_user_api_tokens_by_token_id_regenerate(TOKEN_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…" });
const data = await client.account.postUserApiTokensByTokenIdRegenerate(TOKEN_ID);
{
"data": {
"token": "pa_live_R7wYtL2mHc4sB9dEjaGuVi8fQzXk3vNp",
"token_id": "tok_4b9d2f81a6c35e07",
"name": "CI forecaster",
"scopes": ["models.read", "trainings.read", "inferences.create"],
"last_used_at": null,
"regenerated_at": "2026-07-16T10:03:17.442916+00:00",
"expires_at": "2027-01-12T10:03:17.442916+00:00",
"status": "active",
…
"message": "Token regenerated successfully - save it securely"
}
}Same rule as creation: the new token value is shown only in this
response. Update every caller before the old deploys roll — there's no
grace period for the previous secret.
A 404 ("Token not found or regeneration failed") means the ID doesn't
exist, isn't yours, or was revoked — revoked tokens can't be regenerated.

