Skip to content

ts client ​

Register, show, rotate the secret of, or revoke a client: the credential a script, a CI job, or a customer portal signs in with instead of a person. Reach for rotate before a secret expires or as soon as one leaks, and for delete when the job is retired.

Usage ​

text
ts clients [--csv] [--no-trunc]
ts client <client-id>
ts client new <client-id> --service-account=ACCOUNT [--lifetime-days=DAYS]
ts client new <client-id> --customer-portal [--lifetime-days=DAYS]
ts client rotate <client-id> [--lifetime-days=DAYS] [--overlap-days=DAYS]
ts client delete <client-id> [--force]

Description ​

A client is an OAuth client registered with this server. It signs in with its client ID and a secret, and acts as its own service account, never as the person who registered it. ts clients lists the clients that are not revoked, with the service account each acts as, when it was registered and last signed in, and its ID. The singular forms name a client by its client ID, in any case. The same clients appear in the Administration site under Identity → Clients.

Service Accounts ​

new takes the account the client acts as in --service-account. A name no account holds creates a service account with that name and no groups; the name of an existing service account binds the client to it, so several clients can share one. The login of a person's account is refused. The service account's groups decide what the client can reach, by the same rules as a person's account, so give it the groups the job needs with ts group add. A client stops signing in when it is revoked, and when its service account is disabled or deleted.

Secrets ​

new and rotate print the secret once. The server keeps only a hash of it, so a lost secret cannot be shown again; rotate it instead. With --format=json, the result's client object carries it as clientSecret, for a script that stores it directly.

Every secret expires, after --lifetime-days: 180 days unless you say otherwise, at most 730. Once it expires, nothing can sign in with it, and rotate issues a new one. ts client <client-id> shows when the secret expires; ts clients does not.

rotate issues a new secret and keeps the replaced one working for --overlap-days, 7 days unless you say otherwise, at most 30, so every job can move to the new secret before the old one stops. The replaced secret never outlives its own expiry, and with --overlap-days=0 it stops at once. A client has at most two working secrets: rotating again during an overlap stops the secret replaced the time before.

A peer server, a Turbo server that federates from this one and is registered in the Administration site, signs in with its own keys and has no secret, so rotate refuses it.

Customer Portals ​

new --customer-portal registers a customer portal's client. It acts as no account: its token is good only for asking for launches for the portal's visitors, so it cannot run ts commands. The visitors use applications as guests of a group that registering the client creates, named after it: Guests of shop-portal for the client shop-portal. Give the guests access by adding that group to a workspace or an application. --service-account is refused beside --customer-portal.

Signing In as a Client ​

A script signs in as a client in one of two ways:

  • Set TSCLIENTID and TSCLIENTSECRET. Every command exchanges them for a fresh token and stores nothing, so this is the form for CI and for anything that runs longer than a few minutes. TSACCESSTOKEN, when it is set, wins over them.
  • Run ts login --client-id=ID --client-secret=SECRET once. ts keeps only the access token, which lasts five minutes and cannot be renewed; after that, the next command asks you to run ts login again.

Another program signs in the same way: a client_credentials request for the scope turbo at the token endpoint that https://<server>/api/.well-known/openid-configuration names.

Revoking a Client ​

delete asks first, then revokes the client: it signs in no more, and a customer portal's group of guests is deleted, so their visits end at their next request. A revoked client's ID stays taken; register its replacement under another ID.

Registering, rotating, and revoking are recorded in the operator audit log with the acting administrator, as OAuthClientCreated, OAuthClientSecretRotated, and OAuthClientRevoked.

Options ​

FlagDescriptionDefault/ValuesNotes
--service-accountThe account the client acts as.Login namenew only, and required there unless --customer-portal is given. Created on first use.
--customer-portalRegister a customer portal's client, which launches its visitors as guests.new only. Refused beside --service-account.
--lifetime-daysDays until the new secret expires.180; 1 to 730new and rotate.
--overlap-daysDays the replaced secret keeps working.7; 0 to 30rotate only. Never past the replaced secret's own expiry.
--force, -fRevoke without confirming.delete only. Required when there is no terminal.
--csvTab-separated output, nothing truncated.ts clients only.
--no-truncDo not truncate columns.ts clients only.

Examples ​

bash
# Which clients are registered, and which account does each act as?
ts clients

# One client, including when its secret expires
ts client ci-deploy

# Register a CI job's client, then give its service account a group
ts client new ci-deploy --service-account=svc-deploy
ts group add Deployers svc-deploy

# Use it from CI: each command exchanges the secret for a fresh token
export TSCLIENTID=ci-deploy
export TSCLIENTSECRET="$CI_DEPLOY_SECRET"
ts workspaces

# Rotate a secret before it expires, and give the jobs two weeks to move to the new one
ts client rotate ci-deploy --overlap-days=14

# A secret leaked: replace it and stop the old one now
ts client rotate ci-deploy --overlap-days=0

# Register a customer portal's client with a one-year secret
ts client new shop-portal --customer-portal --lifetime-days=365

# Retire a client
ts client delete nightly-report --force

# Who rotated a client's secret this month?
ts audit --event=OAuthClientSecretRotated --since=2026-10-01
bash
# ts login keeps a five-minute token it cannot renew, so a CI job signed in
# this way fails partway through. Set TSCLIENTID and TSCLIENTSECRET instead.
ts login --client-id=ci-deploy --client-secret="$CI_DEPLOY_SECRET"

Output ​

text
CLIENT ID       SERVICE ACCOUNT  CREATED           LAST USED         ID
ci-deploy       svc-deploy       2026-10-21 09:12  2026-10-21 09:30  7c9e6679-7425-40de-944b-e07fc1f90ae7
nightly-report  svc-reports      2026-08-14 16:05  2026-10-21 02:00  9a1f3e52-6c08-4b7d-b2e4-5d81c0f6a913
shop-portal                      2026-09-02 14:40  2026-10-21 08:55  1b4e28ba-2fa1-41d2-883f-0016d3cca427

new:

text
Registered client ci-deploy
Acts as: svc-deploy

Secret:  q3Vn8bXr0dT6yLwP2kZfH1sJ9mCeA4uG7oRiNvQxY5E
Expires: 2027-04-19T09:12:44.5231234Z

This is the only time the secret is shown. Store it now, and rotate it before it expires.

A customer portal's client prints Launches its visitors as guests of the group Guests of shop-portal in place of Acts as.

rotate:

text
Rotated the secret of client ci-deploy

Secret:  Lk2pW9xQ4cZr7tVb1mNe8sHd3fJy6gAu0oRiTqXw5CE
Expires: 2027-04-19T10:03:17.0861452Z
The previous secret works until 2026-10-28T10:03:17.0861452Z.

This is the only time the secret is shown. Store it now.

With no overlap left, the third line reads The previous secret has stopped working.

ts client <client-id>:

text
Client id:       ci-deploy
Id:              7c9e6679-7425-40de-944b-e07fc1f90ae7
Service account: svc-deploy
Created:         2026-10-21T09:12:44.5231234
Last used:       2026-10-21T09:30:02.1187310
Secret expires:  2027-04-19T09:12:44.5231234

A customer portal's client shows none (customer portal) as its service account, and a client that has never signed in shows never as last used.

Exit Codes ​

0 on success. -1 when the client ID is missing; when new has both --service-account and --customer-portal, or neither; when a lifetime or overlap is out of range; when the client ID is taken, including by a revoked client, or is longer than 100 characters; when the service account is a person's login; when rotate names a peer server; and when you answer no at the prompt, or there is no terminal and --force was not passed. -3 when the server cannot be reached. -4 when no account is signed in. -5 when the account is not a Server Administrator. -6 when no client that is not revoked has that client ID.

  • ts login — Sign in as a client with --client-id and --client-secret.
  • ts group — Give a client's service account the groups its job needs.
  • ts publisher — Let a GitHub Actions workflow publish releases with no secret at all.
  • ts audit — Who registered, rotated, or revoked a client, and when.