Expensicat
CLI

Configuration

Environments, config file, global flags, and credential storage

Global flags

Available on every command:

FlagDescription
--env <name>Override environment for this command (production, staging, local)
--describePrint machine-readable command schema as JSON and exit
--no-auto-updateSkip the background update check for this invocation
--helpShow help for the command
--versionPrint the CLI version

Destructive commands (anything that deletes) accept --yes / -y:

FlagDescription
-y, --yesSkip 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 --yes

Output and pagination flags on every resource command:

FlagDescription
--jsonOutput JSON instead of a formatted table
--tableForce 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-allAuto-paginate and stream every page (list commands)

Environments

Three built-in environments map to API URLs:

EnvironmentAPI URL
productionhttps://api.expensicat.com
staginghttps://api-staging.expensicat.com
localhttp://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 staging

Config file

The CLI stores config at ~/.expensicat/config.toml:

env = "production"
api_url = "https://api.expensicat.com"
default_format = "auto"
default_org_id = "org_..."
KeyDefaultDescription
envproductionActive environment
api_urlResolved from envOverride the API URL directly
default_formatautoauto (table in TTY, JSON when piped), json, or table
default_org_idDefault organization for all requests
auto_updatetrueSet 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 edit

Credentials

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 table

Interactive 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.jsonl

Flags

FlagDescription
--batch <path|->Input source — - for stdin, otherwise a file path
--concurrency <n>Parallel workers (1–10, default 1)
--fail-fastStop 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

CodeMeaning
0All items succeeded
9 (PARTIAL_FAILURE)Some items succeeded, some failed
1All items failed
2Usage error (e.g. --batch conflicting with a per-item flag like --name)
7Input 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:

FullAliasExample
listlsexpensicat invoice ls
deletermexpensicat invoice rm inv_abc123
showgetexpensicat invoice get inv_abc123
createnewexpensicat 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 spawns npm install -g @expensicat/cli@latest in 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 upgrade or the install docs. It never attempts to rewrite a binary it didn't manage.

Force a check at any time with:

expensicat upgrade

Disabling auto-update

Three levers, most-specific to least:

LeverScope
--no-auto-update global flagThis invocation only
EXPENSICAT_DISABLE_AUTOUPDATER=1 env varCurrent shell / process tree
auto_update = false in ~/.expensicat/config.tomlPersistent, 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

VariableEffect
EXPENSICAT_TOKENUse this bearer token instead of the stored credentials (useful in CI)
EXPENSICAT_DISABLE_AUTOUPDATERSet to 1 to skip the background update check
NO_COLORStandard — disable ANSI colors (also respected automatically when piped)
FORCE_COLORStandard — force ANSI colors even when piped
CIStandard — 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 --describe

Every command, option, type, and default is included — no scraping help text required.

On this page