Auth

The CLI authenticates with an organization-scoped API key. Credentials live in an owner-only file at ${XDG_CONFIG_HOME:-~/.config}/khotan/profiles.json (0600, in a 0700 directory), or in environment variables, or in a repo-local .env.khotan.local. Never put an API key in a file committed to the repository.

Store an existing key

Create a key in the dashboard, then:

khotan auth set-key --api-url https://app.example.com --api-key khk_live_...
khotan whoami

Use a per-customer profile name, not default, when one machine talks to more than one organization.

Exchange credentials for a key

khotan login \
  --api-url https://app.example.com \
  --email you@example.com \
  --password '••••••' \
  --organization-id org_123

login calls POST /api/v1/api-keys, stores the minted key once, and verifies it. The key itself is never printed to stdout.

Profiles

khotan auth set-key --profile staging --api-url https://staging.example.com --api-key ...
khotan auth use staging
khotan auth list
khotan apps list --profile staging

auth list never prints keys.

Environment variables

Environment variables take precedence over stored profile values, so CI and agent sandboxes need no on-disk state:

Variable Purpose
KHOTAN_API_URL API origin
KHOTAN_API_KEY Organization-scoped API key
KHOTAN_PROFILE Select a stored profile by name
KHOTAN_ORG_ID Expected org id; the CLI asserts whoami.organizationId matches it
export KHOTAN_API_URL=https://app.example.com
export KHOTAN_API_KEY=khk_live_...
khotan apps list --json

Repo-local org isolation

When .env.khotan.local sits at or above the working directory, the CLI auto-loads its KHOTAN_* values so every command is scoped to that repo's organization. Precedence is flag > .env.khotan.local > shell env > profile.

# .env.khotan.local (git-ignored)
KHOTAN_API_URL='https://<workspace>.khotan.com'
KHOTAN_API_KEY='<org-scoped key>'
KHOTAN_ORG_ID='<org id>'

When KHOTAN_ORG_ID or --assert-org <id> is set, every session-bearing command verifies whoami.organizationId before trusting output and fails closed on mismatch (org_mismatch). With no expected org id, behavior is unchanged.

If apps list or context list returns another customer's data, stop. Do not infer deployment or env from CLI output until the org assertion passes. See errors.