Intuit began publishing intuit-cli in April 2026. Version 0.2.4 landed in
July. It is an official command-line client for the QuickBooks Online API, and
since we spend most of our time driving that API, we installed it and worked
through the surface. These are our technical notes.
Tested on 28 August 2026: intuit-cli@0.2.4, Node 24.18, Windows 11,
authenticated against an Intuit sandbox company. No client file was touched,
and the only write attempted was a --dry-run.
Setup
npm install -g intuit-cli
intuit auth login --profile sandbox --env sandbox
Two things to know before you start:
The sandbox redirect URI is hardcoded to http://localhost:9477/callback, so
you have to add it to your Intuit developer app settings before the login will
complete. Production requires a registered HTTPS URI: localhost is rejected there.
Credentials come from a .env file resolved against the current working
directory (import "dotenv/config" at dist/cli.js:2). Tokens themselves go
somewhere sane: ~/.config/intuit-cli/, AES-256-GCM with OS keychain backing.
However, the client ID and secret are working-directory dependent, so the same
command run from two different directories can hit two different companies.
That is worth knowing if you script it.
The environment variables are INTUIT_SANDBOX_CLIENT_ID / INTUIT_SANDBOX_CLIENT_SECRET
and their INTUIT_PROD_ equivalents. Login prints an authorization URL, listens
on port 9477, and writes a named profile when the callback lands.
What works
The read side is solid. auth status prints a table showing not just the state
but the source of every setting (which file or environment variable each value
came from), which is a clearer diagnostic than most CLIs provide:
Profile zeno-sandbox (active) profiles.json
Credentials Configured INTUIT_SANDBOX_CLIENT_* env vars
Access Token Valid (60m remaining) ~/.config/intuit-cli/...
Twenty entity nouns follow a consistent intuit <noun> <verb> grammar.
--all auto-paginates. --csv dumps every raw column including sync tokens and
metadata timestamps, with nested references JSON-escaped inline. Named profiles
handle multi-company work, with --profile as a per-command override.
intuit query passes a statement straight to the QuickBooks Online query endpoint
and returns raw QueryResponse JSON. For answering an ad-hoc question about a
file without writing ad-hoc script code, it is fast.
The webhooks guide, listen, and replay commands also provide a clean local
development workflow for testing inbound event webhooks.
--where is an unvalidated passthrough
The README’s own example fails:
$ intuit customers list --where "Balance > 0"
Error: 400 Error parsing query — QueryParserError: Encountered " ">" "> ""
at line 1, column 38.
With --debug enabled, you can see why: the clause is interpolated verbatim into
the query string, and QuickBooks Online’s parser does not accept > on Balance.
The operators listed as valid by the API are =, !=, like, in, between,
and contains.
So --where is not a filter abstraction; it is a raw string splice. You need to
know the QuickBooks Online SQL dialect to use it. If you keep to supported syntax,
--where "DisplayName LIKE 'A%'" works fine.
API errors abort the process on Windows
Every error returned by the Intuit API (a bad ID, a malformed query) aborts with a libuv assertion rather than exiting cleanly:
Error: Intuit API error: 400 ... intuit_tid: 1-6a91d2e9-23b60b8857e1b64e3d3d6395
Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 94
Reproduced 3 out of 3 times. The exit code is 127, not 1.
That distinction matters if you wrap the CLI in scripts. Exit code 127
conventionally signals “command not found”, so a parent process cannot
distinguish an API validation failure from a missing binary. Local errors (bad
profile name, unauthenticated state) exit cleanly with 1, so the crash is
specific to response handling from Intuit’s endpoints. We only observed this on
Windows; the likely culprit is the native @napi-rs/keyring handle tearing down
mid-close.
An unrelated version detail: intuit --version reports 0.1.0. It is hardcoded
at dist/cli.js:118 while the published npm package is 0.2.4.
--dry-run covers 3 of 36 write verbs
This is the most significant architectural observation, and it is not a bug.
--dry-run prints the outbound request payload without sending it:
$ intuit customers create --display-name "Acme" --idempotency-tag run-b3a7 --dry-run
[dry-run] POST /customer
{
"DisplayName": "Acme",
"Notes": "[via Intuit CLI · run run-b3a7]"
}
Counting every create, update, void, and delete subcommand that actually
exists across the CLI’s entity commands, there are 36 write verbs. Only three
accept --dry-run: customers create, vendors create, and invoices create.
--idempotency-tag has the exact same three-command footprint, appending its
marker to the record’s Notes field.
All three supported verbs are creates. Every update, void, and delete command posts directly without a preview option.
That is a coherent design choice for a developer tool. --dry-run answers: did
I construct the payload I intended to construct?, which is a helpful debugging
check when building a create script against a sandbox. It was not intended to be
a human approval layer.
However, it does mean previews are provided only for the operation that is easiest to undo (creating a new draft record), and entirely absent from the operations that modify existing records or remove them. If you point the CLI at a production company file, update and delete calls post immediately without confirmation or attribution tags. Whatever review checks you need around those actions must be built outside the tool.
Reproducing the verb count
To verify the subcommand count independently:
for n in customers vendors invoices bills payments items estimates purchases \
deposits salesreceipts creditmemos billpayments employees; do
verbs=$(intuit $n --help | sed -n '/Commands:/,$p' | awk '{print $1}' \
| grep -E '^(create|update|void|delete)$')
for v in $verbs; do
intuit $n $v --help | grep -q -- '--dry-run' && echo "$n $v: dry-run"
done
done
(Note: a naive loop over help output will report 52 verbs instead of 36 because
running intuit <noun> void --help on entities that lack void falls back to
displaying parent help. Parsing the Commands: block explicitly avoids this.)
Summary
intuit-cli is a capable first-party tool for developers, and the read side
(query, --csv, auto-pagination, named profiles) is immediately useful for
inspecting sandbox company data. The exit-code crash on Windows is the primary
issue to guard against if automating around it today.