Skip to main content
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 --example and qube workflows install --replace.
  • v0.5.0: qube connections create --webhook-url, qube workflows decide and search, and qube check.
  • v0.6.0: qube skills install, and connection events in qube listen.
  • v0.7.0: qube simulator faults --stall.

Install

Or download the archive for macOS, Linux or Windows (amd64 and arm64) from the releases page, unpack it, and put 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

--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.
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.
--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.
If the app already has a workflow at that key, the install is refused with 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.
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.
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:
Pass the URL it prints as the 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 .env once. 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, and qube listen says so.
  • One listener per URL. Starting qube listen again, 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).
See Receive webhooks in development for a tunnel instead.

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.
The URL is the base your app builds each webhook URL from, 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:
The deliveries are fake: their ids and references say 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. 1 when a check fails, 0 otherwise. 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.com is refused, and so is a qube listen URL: 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 every qube 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 run qube. 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.