Configuration
Environments, config file, global flags, and credential storage
Global flags
Available on every command:
| Flag | Description |
|---|---|
--env <name> | Override environment for this command (production, staging, local) |
--describe | Print machine-readable command schema as JSON and exit |
--no-auto-update | Skip the background update check for this invocation |
--help | Show help for the command |
--version | Print the CLI version |
Destructive commands (anything that deletes) accept --yes / -y:
| Flag | Description |
|---|---|
-y, --yes | Skip the "are you sure?" confirmation prompt |
In a terminal, destructive commands prompt before proceeding. In non-interactive contexts (CI, pipes), the prompt is impossible — --yes is required, otherwise the command aborts with a clear error. This makes scripts deterministic: they either have explicit consent or they fail fast.
# TTY — prompts, you confirm
expensicat invoice delete inv_abc123
# TTY — skip the prompt
expensicat invoice delete inv_abc123 --yes
# CI / script — explicit consent required
expensicat invoice delete inv_abc123 --yesOutput and pagination flags on every resource command:
| Flag | Description |
|---|---|
--json | Output JSON instead of a formatted table |
--table | Force table output (overrides auto-detection when piped) |
--fields <a,b,c> | Comma-separated fields to include in output |
--columns <a,b,c> | Comma-separated columns for table rendering |
--cursor <cursor> | Pagination cursor (from previous meta.next_cursor) |
--limit <n> | Page size (list commands) |
--fetch-all | Auto-paginate and stream every page (list commands) |
Environments
Three built-in environments map to API URLs:
| Environment | API URL |
|---|---|
production | https://api.expensicat.com |
staging | https://api-staging.expensicat.com |
local | http://localhost:8989 |
Pick one per-command with --env, or persist it:
# One-off
expensicat --env staging invoice list
# Persisted for all future commands
expensicat config set env stagingConfig file
The CLI stores config at ~/.expensicat/config.toml:
env = "production"
api_url = "https://api.expensicat.com"
default_format = "auto"
default_org_id = "org_..."| Key | Default | Description |
|---|---|---|
env | production | Active environment |
api_url | Resolved from env | Override the API URL directly |
default_format | auto | auto (table in TTY, JSON when piped), json, or table |
default_org_id | — | Default organization for all requests |
auto_update | true | Set to false to disable the background update check entirely |
Managing config
# View everything
expensicat config list
# Read a single key
expensicat config get env
# Update a value
expensicat config set env local
expensicat config set default_format json
# Open in $EDITOR
expensicat config editCredentials
The CLI uses your OS keychain when available (macOS Keychain, Windows Credential Manager, Linux Secret Service) and falls back to a file at ~/.expensicat/credentials.json with 0600 permissions.
Tokens are refreshed automatically from a long-lived session token — you won't be prompted to log in every 15 minutes.
Output format
Read commands auto-detect their output:
- In a terminal — rich formatted tables
- When piped — JSON
Force one or the other:
expensicat invoice list --json
expensicat config set default_format tableInteractive prompts
Create commands (invoice create, customer create, etc.) prompt for missing required fields when run in a terminal. Flags always win — any field passed via flag skips its prompt.
Non-interactive contexts (CI, piped stdin) skip prompts entirely and fail with a validation error if required fields are missing, so scripts stay deterministic.
# Interactive — prompts for anything not passed
expensicat invoice create
# Partial — prompts only for what's missing
expensicat invoice create --customer-id cust_123
# Fully non-interactive — no prompts, fails if anything required is missing
expensicat invoice create --customer-id cust_123 --due-date 2026-05-01 \
--items '[{"name":"Consulting","quantity":1,"price":5000}]'Batch input
Streaming JSONL (or a JSON array) into one CLI invocation is the primary way
to create records in bulk — ideal for AI agents or scripted imports. Supported
on every create subcommand that declares batchable: true (inspect via
expensicat --describe).
# JSONL from stdin
cat customers.jsonl | expensicat customer create --batch -
# JSON array from a file
expensicat task create --batch ./tasks.json
# Parallel, streaming results as JSONL for piping
expensicat entry create --batch - --concurrency 5 --json < entries.jsonlFlags
| Flag | Description |
|---|---|
--batch <path|-> | Input source — - for stdin, otherwise a file path |
--concurrency <n> | Parallel workers (1–10, default 1) |
--fail-fast | Stop at the first failure (default: collect errors and continue) |
Input formats
- JSONL (default) — one JSON object per line. Empty lines and lines starting with
#are skipped, so you can comment or paginate through a file. - JSON array — auto-detected when the first non-whitespace character is
[. Easier to paste, but the whole input is buffered in memory.
Output
-
In a terminal — a running spinner and a failure summary at the end.
-
With
--json— one result object per line (JSONL, streamable), regardless of success:{"ok":true,"data":{"id":"cust_abc","name":"Acme"},"input":{"name":"Acme"},"line":1} {"ok":false,"error":{"code":"VALIDATION_ERROR","message":"email: Invalid email"},"input":{"name":"Widget"},"line":2}
Exit codes
| Code | Meaning |
|---|---|
0 | All items succeeded |
9 (PARTIAL_FAILURE) | Some items succeeded, some failed |
1 | All items failed |
2 | Usage error (e.g. --batch conflicting with a per-item flag like --name) |
7 | Input could not be parsed at all (malformed JSON array) |
Caveats
- Batch mode bypasses interactive prompts — each item must be self-contained.
- Per-item flags (e.g.
--name) are rejected alongside--batch— put those values in the input file instead. - Rate limits from the API are handled by the SDK's built-in exponential backoff — you don't need to throttle yourself.
Aliases
Common verb aliases mirror familiar shell conventions. Use whichever feels natural:
| Full | Alias | Example |
|---|---|---|
list | ls | expensicat invoice ls |
delete | rm | expensicat invoice rm inv_abc123 |
show | get | expensicat invoice get inv_abc123 |
create | new | expensicat customer new --name "Acme" |
Updating
The CLI keeps itself current. How it updates depends on how you installed it:
- Installed via
npm i -g— on startup, the CLI checks the npm registry once a day. When a new version is available, it spawnsnpm install -g @expensicat/cli@latestin the background (detached) and prints a single-line stderr notice. The new binary is in place for your next invocation; the current session continues on the old version so nothing is interrupted mid-command. - Installed via a platform binary or other package manager — the CLI prints a one-line stderr notice when a newer version exists, pointing you at
expensicat upgradeor the install docs. It never attempts to rewrite a binary it didn't manage.
Force a check at any time with:
expensicat upgradeDisabling auto-update
Three levers, most-specific to least:
| Lever | Scope |
|---|---|
--no-auto-update global flag | This invocation only |
EXPENSICAT_DISABLE_AUTOUPDATER=1 env var | Current shell / process tree |
auto_update = false in ~/.expensicat/config.toml | Persistent, all future runs |
The background update check also fails silently if the network is unreachable, the npm registry is slow, or the install prefix isn't writable — auto-update never blocks or errors the command you actually ran.
Environment variables
| Variable | Effect |
|---|---|
EXPENSICAT_TOKEN | Use this bearer token instead of the stored credentials (useful in CI) |
EXPENSICAT_DISABLE_AUTOUPDATER | Set to 1 to skip the background update check |
NO_COLOR | Standard — disable ANSI colors (also respected automatically when piped) |
FORCE_COLOR | Standard — force ANSI colors even when piped |
CI | Standard — when set, the CLI treats the session as non-interactive (skips prompts) |
Machine-readable schema
For AI agents and scripts, --describe outputs the full command tree as JSON:
expensicat --describeEvery command, option, type, and default is included — no scraping help text required.