Configuration, profiles and the API key

September 20, 2026 ยท View on GitHub

Everything jev needs to make a call (a key, a base URL, a model, a timeout) can come from a flag, from the environment, or from a profile in one configuration file. This page says where each value comes from, how to change it, and where the key is kept.

Where a value comes from

flag > environment variable > profile > built-in default. Every command resolves its settings the same way, and jev config get shows the winner and where it came from:

jev config get model                 # the value, its source and its origin
jev config get model --field value   # just the value
jev config list                      # every setting, with its source

jev config list is the quickest answer to "why did that run use the wrong model?".

The settings

SettingFlagEnvironmentDefaultWhat it does
base_url--base-urlTYPESAFE_BASE_URLhttps://api.typesafe.aiAPI root. Must be https://, unless it is loopback or --insecure-allow-http is given.
model--modelTYPESAFE_DEFAULT_MODELjev-latestModel alias or versioned id. Pin a versioned id for reproducible answers and a known price.
output--outputJEV_OUTPUTtable on a terminal, json when pipedtable, json, yaml or jsonl.
timeout--timeout30sTime allowed per attempt, e.g. 500ms, 30s, 2m.
max_retries--max-retries2Retries after the first attempt (408, 429, 5xx and connection errors only).
concurrency--concurrency4Parallel requests in a batch run. Above roughly 8, a shared key gets rate limited.
warn_unpinned--warn-unpinnedfalseRemind me on stderr when a request used an alias instead of a versioned model id.
update.autoJEV_AUTO_UPDATEtrueUpdate jev automatically in the background.
update.channelstableReleases to follow: stable or prerelease.
update.pin_versionunsetStay on one version; this turns automatic updates off.

Each profile has its own copy of the first seven. The update.* settings are shared by every profile, because a machine has one jev binary.

jev config set model jev-1.13.0      # store it in the selected profile
jev config unset model               # back to the default
jev config set update.auto false     # for every profile

The API key is not a setting: it never goes into the configuration file. See The API key below.

The configuration file

jev config path prints it. By default:

PlatformDirectory
Linux and other Unix$XDG_CONFIG_HOME/jev, or ~/.config/jev
macOS~/Library/Application Support/jev
Windows%APPDATA%\jev

JEV_CONFIG_DIR moves the whole directory, which is how CI jobs and tests keep out of a person's real configuration. jev config path works even when the file is broken, so it is the way to find a file that needs fixing.

The file is TOML, and editing it by hand is fine: jev config set keeps your comments and only writes the keys you asked for:

# jev configuration. Change it with `jev config set` and `jev profile`, or by hand.
# API keys are never stored in this file.
schema_version = 1
active_profile = "ci"

[profiles.default]
model = "jev-1.13.0"

[profiles.ci]
model = "jev-1.13.0"
output = "json"
timeout = "60s"

[update]
channel = "prerelease"

Profiles

A profile is a named set of defaults plus its own stored API key: one for your own account, one for a team key, one for a staging endpoint.

jev profile create ci --model jev-1.13.0 --output json
jev config set --profile ci timeout 60s
jev auth login --profile ci            # its own key
jev profile use ci                     # make it the one used by default
jev profile list                       # which exist, which is active
jev noul "Is this spam?" --state "$MESSAGE" --profile default   # for one run

--profile wins over JEV_PROFILE, which wins over the active profile set by jev profile use. jev profile delete ci removes the profile, its settings and its stored key; default cannot be deleted.

The API key

TypeSafe authenticates with a bearer API key, and jev looks for it in exactly two places:

  1. TYPESAFE_API_KEY in the environment. It always wins, and it is what CI jobs, containers and agents should use.
  2. The credentials file that jev auth login writes next to config.toml. It is created with mode 0600 in a 0700 directory, and jev refuses to read it when others can, telling you the chmod to run.

There is no OS keychain, no OAuth, no SSO and no browser flow. The key is never accepted as a flag value, so it cannot end up in your shell history or in a process list: jev auth login prompts for it without echoing, or reads it from stdin with --with-token.

jev auth login                              # prompts, checks the key, stores it
printf '%s' "$KEY" | jev auth login --with-token
jev auth status                             # which key is in use, and whether the API accepts it
jev auth status --offline                   # without calling the API
jev auth logout                             # delete the stored key for this profile

Only the last four characters of a key are ever shown (fingerprint), and the key never appears in output, logs, errors, --dry-run output or MCP results, --debug-bodies and -vv included.

In an environment without a terminal, jev never prompts: with stdin not a TTY, --no-input, JEV_NO_INPUT or CI=true it fails with an error naming the flag or variable to use instead.

Environment variables

VariableWhat it does
TYPESAFE_API_KEYThe API key. Wins over a stored one.
TYPESAFE_BASE_URLAPI root, as --base-url.
TYPESAFE_DEFAULT_MODELDefault model, as --model.
TYPESAFE_LOG_LEVELHow much jev logs to stderr (error, warn, info, debug, trace), as --verbose does.
JEV_OUTPUTOutput format, as --output.
JEV_PROFILEProfile to use, as --profile.
JEV_NO_INPUTNever prompt, as --no-input.
JEV_CONFIG_DIRThe configuration directory, holding config.toml and credentials.
JEV_AUTO_UPDATEfalse turns the background update check off for one environment.
CItrue turns prompts and background updates off.
NO_COLORAny value disables colour, as --no-color.

The first four are the variables TypeSafe's own SDKs read, so a shell already set up for them works with jev unchanged. Settings that only jev has use the JEV_ prefix.

Updates

jev updates itself only when the install script installed it. To see what applies now:

jev version            # version, how it was installed, and whether it updates itself
jev update --check     # exit 20 when a newer release exists; changes nothing

To turn automatic updates off, any one of these is enough:

jev config set update.auto false             # for good, on this machine
export JEV_AUTO_UPDATE=false                 # for one environment
jev config set update.pin_version 0.3.1      # stay on one version

It is already off for Homebrew and cargo installs (they are updated by their package manager), when CI=true, and when jev cannot write to its own directory. The only host it contacts for this is GitHub Releases of shaharia-lab/jev-cli; every download is verified against signing keys compiled into the binary before anything is replaced. See Security and privacy.