kscli uses JWT-based authentication via admin impersonation. An admin API key is used to obtain a short-lived JWT token for a specific user/tenant pair, and the token is cached locally for reuse.
┌──────────────┐
kscli assume-user │ ks-backend │
--tenant-id T │ │
--user-id U ──────────> │ POST │
X-Admin- │ /v1/auth/ │
Api-Key │ assume_user │
└──────┬───────┘
│ { "token": "<jwt>" }
v
┌─────────────────────┐
│ ~/.credentials file │
│ (0600 permissions) │
│ │
│ token, user_id, │
│ tenant_id, │
│ expires_at, │
│ admin_api_key │
└─────────┬───────────┘
│
subsequent commands │ auto-loaded
v
┌─────────────────────┐
│ ksapi.ApiClient │
│ cookie: ks_uat=JWT │
└─────────────────────┘
Authenticates as a specific user in a specific tenant.
kscli assume-user --tenant-id <tenant-uuid> --user-id <user-uuid>What it does (src/kscli/commands/auth.py:19-25):
- Reads
ADMIN_API_KEYfrom env or config (src/kscli/config.py:33-42) - Sends
POST /v1/auth/assume_userwithX-Admin-Api-Keyheader (src/kscli/auth.py:26-34) - Decodes the JWT (without verification) to extract the expiration claim (
src/kscli/auth.py:43) - Writes the credentials file with
0600permissions (src/kscli/auth.py:46-57)
Output:
Authenticated as user 00000000-... in tenant 00000000-...
Token expires: 2026-02-23T12:00:00+00:00
Shows the currently authenticated identity by calling the /users/me API endpoint.
kscli whoamiOutput (table format):
┌─────────────┬──────────────────────────────────────┐
│ Key │ Value │
├─────────────┼──────────────────────────────────────┤
│ id │ 00000000-0000-0000-0001-000000000001 │
│ email │ user@example.com │
│ tenant_id │ 00000000-0000-0000-0002-000000000001 │
│ expires_at │ 2026-02-23T12:00:00+00:00 │
└─────────────┴──────────────────────────────────────┘
Credentials are stored at /tmp/kscli/.credentials by default (override with KSCLI_CREDENTIALS_PATH).
File format (JSON):
{
"token": "<jwt>",
"user_id": "00000000-...",
"tenant_id": "00000000-...",
"expires_at": "2026-02-23T12:00:00+00:00",
"admin_api_key": "your-admin-key"
}The admin_api_key is persisted alongside the token so that automatic token refresh works without requiring the env var to be set on every invocation.
When any command calls get_api_client() (src/kscli/client.py:38-50), it triggers load_credentials() (src/kscli/auth.py:60-80) which:
- Loads the credentials file
- Compares
expires_atagainst the current UTC time - If expired, calls
assume_user()again using the cachedadmin_api_key,tenant_id, anduser_id - Returns the refreshed credentials transparently
This means long-running scripts or interactive sessions never break due to token expiry.
Authentication-related errors produce specific exit codes (src/kscli/client.py:24-30):
| HTTP Status | Message | Exit Code |
|---|---|---|
| 401 | Session expired. Run: kscli assume-user ... |
2 |
| 403 | Permission denied |
1 |
If you see exit code 2, re-run assume-user to re-authenticate:
kscli assume-user --tenant-id <id> --user-id <id>For environments using self-signed certificates or corporate proxies:
# Use a custom CA bundle
export KSCLI_CA_BUNDLE=/path/to/ca-bundle.crt
# Disable SSL verification (development only)
export KSCLI_VERIFY_SSL=falseSSL configuration is resolved in get_tls_config() (src/kscli/config.py:63-83) and applied when building the API client (src/kscli/client.py:43-48).
If SSL verification fails, the CLI prints troubleshooting steps (src/kscli/client.py:88-98):
Error: SSL certificate verification failed.
Solutions:
1. Install Python certificates (macOS): Run 'Install Certificates.command'
2. Set KSCLI_CA_BUNDLE to your custom CA bundle path
3. (Insecure) Set KSCLI_VERIFY_SSL=false for development