Skip to content

Rotate service API tokens ​

API tokens (psk_…) authenticate a workload as a User or ServiceIdentity. plexctl identity-tokens wraps /v1/auth/tokens. The plaintext is shown exactly once — capture it on issue/rotate.

Prerequisites ​

Steps ​

Issue a token ​

shell
plexctl identity-tokens issue \
  --identity-ref service:<service-uuid> \
  --env-prefix prod \
  --ttl 720h
# WARNING: this is the only time this plaintext will be displayed
# psk_prod_…
# ID  ENV   CREATED_AT  EXPIRES_AT  LAST_USED_AT  ROTATED_AT
# …   prod  …           …           -             -

--identity-ref is user:<uuid> or service:<uuid>. The response prints the plaintext once under a one-time-plaintext banner; pipe it straight to the consuming secret store.

List and revoke ​

shell
plexctl identity-tokens list
plexctl identity-tokens delete --id <token-uuid>

list never returns plaintext.

Rotate a token ​

shell
plexctl identity-tokens rotate --id <token-uuid>

Rotate returns a new token with its own id and prints its plaintext once. Roll the new value out before the response leaves your terminal. The old plaintext keeps authenticating until the rotation_deadline the command prints, 48 hours after the rotation; after that only the new plaintext works.

Rotate refuses a token that is revoked, expired, or already rotated, and plexctl exits 1. A token rotates once: to rotate again, rotate its successor (the id the previous rotate printed).

Recover a rotation that left no successor ​

If rotate fails with a server error, the server may have marked the token as rotated without minting its successor. list then shows the token with rotated_at set, no new token appears, and a second rotate answers 409. The old plaintext still works until its sunset_at. Issue a new token from an interactive session, roll it out, then revoke the old one:

shell
plexctl identity-tokens issue \
  --identity-ref service:<service-uuid> \
  --env-prefix prod \
  --ttl 720h
plexctl identity-tokens delete --id <old-token-uuid>

Revoke a successor minted from a revoked token ​

Older server versions rotated a revoked token and minted a successor for it. That successor stays live: list shows it like any other live token, without revoked_at or sunset_at, and the Tokens tab in the Console (Identities → the service identity → Tokens) shows it as Active. To find one, look for a token whose revoked_at is earlier than its rotated_at; its successor is the token whose created_at equals that rotated_at. Revoke the successor unless you mean to keep it:

shell
plexctl identity-tokens delete --id <successor-uuid>

A Domain admin can also revoke it from the Tokens tab. The tab issues, rotates, and revokes a service identity's tokens, and the server refuses a rotation there for the same reasons it refuses one from plexctl.

Mint an OAuth2 service token ​

shell
plexctl identity-tokens issue-service \
  --client-id <client> \
  --grant-type client_credentials \
  --client-secret @/tmp/client-secret \
  --scope "domains:read"
# TOKEN_TYPE  EXPIRES_IN  SCOPE         ACCESS_TOKEN
# Bearer      3600        domains:read  ***

Verification ​

shell
plexctl identity-tokens list --output json \
  | jq '.items[] | {id, rotated_at, sunset_at, revoked_at}'
# {
#   "id": "<successor-uuid>",
#   "rotated_at": null,
#   "sunset_at": null,
#   "revoked_at": null
# }
# {
#   "id": "<token-uuid>",
#   "rotated_at": "…",
#   "sunset_at": "…",
#   "revoked_at": null
# }

After a rotation the successor is a separate entry with a new id. The rotated-from entry carries rotated_at and sunset_at, the instant its plaintext stops authenticating. revoked_at is set only on a revoked token. An unset key is absent from the output, and jq prints it as null.

See also ​