Appearance
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
TSCLIENTIDandTSCLIENTSECRET. 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=SECRETonce.tskeeps only the access token, which lasts five minutes and cannot be renewed; after that, the next command asks you to runts loginagain.
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
| Flag | Description | Default/Values | Notes |
|---|---|---|---|
--service-account | The account the client acts as. | Login name | new only, and required there unless --customer-portal is given. Created on first use. |
--customer-portal | Register a customer portal's client, which launches its visitors as guests. | new only. Refused beside --service-account. | |
--lifetime-days | Days until the new secret expires. | 180; 1 to 730 | new and rotate. |
--overlap-days | Days the replaced secret keeps working. | 7; 0 to 30 | rotate only. Never past the replaced secret's own expiry. |
--force, -f | Revoke without confirming. | delete only. Required when there is no terminal. | |
--csv | Tab-separated output, nothing truncated. | ts clients only. | |
--no-trunc | Do 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-01bash
# 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-0016d3cca427new:
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.5231234A 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.
Related Commands
- ts login — Sign in as a client with
--client-idand--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.
