qube puts your QuBe Sync account in your terminal. It’s the quickest way to set up a development
environment, and the safest way to let an AI coding agent work against your account: the agent can do
everything below without ever seeing a secret.
Run
qube version to see what you have, and brew upgrade qube to update. Some of this page needs a
recent CLI:- v0.3.0:
qube connections create ... --use,qube use --connection,qube qb RESOURCE create --exampleandqube workflows install --replace. - v0.5.0:
qube connections create --webhook-url,qube workflows decideandsearch, andqube check. - v0.6.0:
qube skills install, and connection events inqube listen. - v0.7.0:
qube simulator faults --stall.
Install
qube on your
PATH. qube version prints what you have; eval "$(qube completion zsh)" (or bash, fish) adds tab
completion.
Log in
qube login shows a short code and opens the dashboard. You confirm the code there, signed in as yourself,
and choose what the session may reach (sandbox apps only by default) and whether it may change anything
(read and write, or read-only). The terminal then receives a session token, stored in your config
directory with owner-only permissions. Nothing is typed into the terminal and nothing is pasted into a chat.
qube login --read-only asks for a read-only session: the approval page preselects it. See
Sessions, scope and access.
qube status shows which host, user and app the CLI is pointed at, and when the session expires, without a
network call. qube whoami asks the server to confirm it.
Set up a project in three commands
qube env --write names the variables it wrote and prints nothing of their values. qube env --print
prints them in full as export lines, if you want to see them.
QuBe Sync keeps only a hash of each API key, so qube env makes a new key for your session, listed on
the app’s page as qube CLI: <session name>. Running it again in the same session replaces that key, and
the one written before stops working. Only an organization’s owners can run it: a member’s session gets
403 owner_only.
If the file already exists, --write changes only its QUBE_* lines: those already there are updated in
place, keeping any export in front, the missing ones are added at the end, and every other line stays
as it was. If .env is a symbolic link, the file it points to is the one updated.
--use makes the new connection the default, so commands that act on a connection don’t need its id:
qube connections list marks the default with *. Deleting a connection always needs it named.
Commands
Apps and sessions
Apps and sessions
Connections and requests
Connections and requests
--webhook-url on connections create or update is sent the connection’s own events:
connection.connected, setup_cancelled, stalled and resumed (Connection events).qube match asks a realtime or simulated connection’s name index which records a name could mean
(Match names). A customer, vendor, employee or other name can be matched on more than
its name: --account-number, --email, --phone and --postal-code can make a strong match, and
--city and --company-name rank the candidates. --parent matches under a parent (a job under a
customer), --min-score drops weaker candidates, and qube match -h lists the kinds and flags. The first match on a connection starts
building its index and is refused until it’s in (exit 1); qube connections name-index starts the
build ahead of time, and --webhook-url is told when it’s ready.QuickBooks operations
QuickBooks operations
qube qb runs every QuickBooks operation in the API reference. It
reads them from the API’s own OpenAPI document, so the CLI offers the flags and body fields your
host has. Query parameters and the body’s top-level fields are flags (max_returned is
--max-returned), and --data takes a whole body as JSON, @file or - for standard input.
Each operation queues a request. --wait waits for QuickBooks’ answer and prints it, every page
of an iterated query, and exits 1 if it failed. It waits up to 10 minutes; use --wait=30m for
longer. Otherwise pass --webhook-url to be sent the answer, or look it up later with
qube requests show. --help --json prints an operation’s full parameters and body schema.--help comes from the same document. Flags that exclude each other are listed together in an
Exactly one of: block (or At most one of: when none is required), with or: between the
alternatives and the flags that go together grouped under each. Help also shows which lists
combine (an invoice’s item and group lines, a journal entry’s debit and credit lines), and which
keys of an object are required. Help ends
with an example built from the operation’s request example in the
API reference, and --example prints that body as JSON for you
to edit and send with --data @file. Tab completion offers each operation’s flags, and the
values of a flag that has a fixed list.The simulator
The simulator
--stall stalls the connection, so you get connection.stalled once a request waits, and
--stall off sends connection.resumed (Test your stall handling).
See Test without QuickBooks.Workflows
Workflows
409 (key_taken), and so
is a second POST /workflow-templates/{key}/install. --replace ("replace": true in the API)
installs again over it: the workflow, and each one it calls that the app already has, gets the
template’s current chart, earlier versions kept, so a setup script can run the same install every
time. --as (as in the API) installs a second copy under another key instead, and
qube workflows push replaces a workflow with a chart file of your own.A template can change after you install it. qube workflows list then says update available in
its TEMPLATE column (update available (edited) when you’ve changed the workflow too), and prints
the install --replace command that takes the current version. For an edited workflow that command
replaces your edits.Runs start only on a realtime or simulated connection: on a standard one, qube workflows run is
refused with 422 realtime_required (Workflows run on realtime connections).See Workflows.Agent skills
Agent skills
qube skills install [NAME] saves a skill QuBe Sync publishes where Claude Code finds it. NAME
defaults to qube-sync-audit, the integration audit. It needs no login, and
running it again updates the file.Anything else
Anything else
qube api calls any v2 path with the current app’s key. A path that doesn’t start with /api/ is
a v2 path. --data takes JSON, @file or - for standard input.qube help lists every command and flag. Every command also takes:
Exit codes:
0 ok, 1 the command failed, 2 wrong usage, 130 interrupted.
--wait and qube requests tail poll, so you can see answers from a terminal or a script. They’re a
developer convenience. Your integration should pass a webhook_url with every request and be told
when it’s answered (Webhooks).Receive webhooks locally
qube listen gives you a webhook_url that reaches the app on your laptop, with no tunnel. It works for
sandbox apps:
webhook_url of your requests and workflow runs, and every delivery to it
is sent on to the --forward-to URL: the body and X-QUBE-Signature byte for byte, so your app
verifies the signature as it will in production. A path or query you
add to the URL is added to the --forward-to URL too. Your app’s answer is the delivery’s outcome, so a
non-2xx is retried and the dashboard shows each attempt, as for any webhook_url.
- One URL per machine. The URL stays the same for an app on this machine, so it goes into
.envonce. The machine is identified by a random id in the CLI’s config folder, shared by every config on it. You can have URLs on up to 10 machines per app; an 11th takes over the one unused longest, which then stops working, andqube listensays so. - One listener per URL. Starting
qube listenagain, in another terminal, takes over: the older one stops and says why. - When it isn’t running, deliveries fail and are retried for about 8 hours like any other, and Resend webhook in the dashboard sends one again. It reconnects by itself when the network drops.
- Ctrl-C stops it (exit
130).
Check your webhook endpoint
qube check webhooks sends signed test deliveries to your own webhook endpoint and grades the answers, so you
find out before QuBe Sync does whether it verifies signatures, refuses
what it should and answers in time. Run it with your app running on your machine:
qube check needs v0.5.0 or later of the CLI. On an older one, qube check answers unknown command:
run qube version, and brew upgrade qube to update.QUBE_WEBHOOK_URL. The CLI signs with the app’s
webhook secret, fetched the way qube env fetches it and kept in memory. It never prints, logs or writes the
secret, or anything your app answers. Each check is one request, and each sends only what your app must refuse
or can’t route, so none reaches a real run or connection:
qube-check, and no record of your app’s matches them.
Every request carries an X-QUBE-Check header naming its check, for your logs. What a check can’t send safely,
such as whether your app verifies the raw body or answers within 5 seconds, the
audit skill checks in your code.
- Exit codes.
1when a check fails,0otherwise. A skipped check isn’t a failure. - It never follows a redirect. A redirect is reported as one, since QuBe Sync doesn’t follow them either, and a request that gets no answer in 10 seconds fails.
- It never sends to QuBe Sync. A URL on
qubesync.comis refused, and so is aqube listenURL: check the local URL you gave--forward-to. - It needs a read and write session. A read-only session can’t read the webhook secret, and neither can an act-as grant.
Production apps
In a production app, every request that changes something asks before it’s sent, and names the app. That covers everyqube qb operation except list, since each one writes to the customer’s real
company file. It also covers creating or changing a connection (its password, onboarding link and
name index too), discarding a request, driving the simulator, every workflow change, run or answer, and
qube api with any method but GET. Reads never ask. Removing something (a connection, a workflow,
a run, a qube listen URL) asks in any app.
A script answers with --yes. Without a terminal, or under --json, a request that would ask is refused
until --yes is given, so nothing reaches a production company file by accident.
Let an AI agent use it
Log in yourself, choosing the default sandbox scope, then let your coding agent runqube. It can
write your .env, create simulated connections, inject faults, push workflows and call the API, but it
never sees the session token or your production apps, and you can revoke its access at any moment. For
an agent that should only look (reading your data, running QuickBooks queries, checking charts), approve
a read-only session instead: the server refuses anything else.
Combined with the MCP server, an agent can explore QuickBooks operations, build requests
and test them end to end on its own.
Sessions, scope and access
A session belongs to you, in one organization.
The server enforces read-only access.
qube status and qube sessions show each session’s access.
Sessions expire after 30 days. qube logout, qube sessions revoke <id>, or CLI sessions in the
dashboard end one sooner, and the CLI stops working at once: it calls the API with the session’s own
token, never the app’s API key. The token is only ever sent to qubesync.com, the host that issued it.
