| name | qbo |
| description | Queries, creates, updates, and manages QuickBooks Online data via the qbo CLI. Use when working with QBO entities (invoices, customers, bills, payments, vendors, accounts, items, estimates, attachables, recurring transactions), running reports, or setting up install, auth, sandbox, and company switching. |
| allowed-tools | Bash(qbo *) |
qbo — QuickBooks Online CLI
Install
brew install voska/tap/qbo
scoop bucket add voska https://github.com/voska/scoop-bucket && scoop install qbo
go install github.com/voska/qbo-cli/cmd/qbo@latest
Getting Credentials
Requires an Intuit Developer account and a QuickBooks app for OAuth credentials.
- Sign up at https://developer.intuit.com and create an app from the dashboard.
- Select QuickBooks Online and Payments as the platform.
- Under Keys & credentials, find your Client ID and Client Secret.
- Add a Redirect URI (see below).
See Intuit's OAuth 2.0 guide for the full walkthrough.
Redirect URI Options
Sandbox allows http://localhost:8844/callback — just register it in the Intuit portal and qbo auth login handles everything automatically.
Production does not allow localhost. Three options:
- Tunnel/funnel address — Route a domain (e.g.
https://auth.yourdomain.com) back to your machine. Register it as the redirect URI. Use --redirect-uri or set QBO_REDIRECT_URI.
- Login on the same machine — If the agent runs on a machine with a browser, use a tunnel so localhost callbacks work.
- Non-resolving domain — Register any domain you own (e.g.
https://yourdomain.com) as the redirect URI. After authorizing, the browser redirects there with ?code=...&realmId=...&state=... in the URL. Copy the full URL from the browser and paste it back into qbo auth login (or provide it to the agent to exchange manually).
Setup
qbo auth set-client --client-id your_client_id --client-secret your_client_secret
qbo auth login --sandbox
qbo auth login --redirect-uri https://yourdomain.com
export QBO_REDIRECT_URI=https://yourdomain.com
qbo auth login
qbo auth login --manual
For non-localhost redirect URIs, qbo auth login prints the auth URL and prompts you to paste the callback URL after authorizing.
Tokens and client credentials (once qbo auth set-client runs, or after the first qbo auth login) are stored in the system keychain (macOS Keychain, Windows Credential Manager) with file-based fallback at ~/.config/qbo/tokens/. Credentials resolve in priority order: env var (QBO_CLIENT_ID/QBO_CLIENT_SECRET) → keychain → config.json, so env vars still work and override the keychain when set. On headless hosts that can't reach the login keychain, set QBO_KEYRING_BACKEND=file to use the encrypted-file backend instead.
Verify
After auth, confirm everything works:
qbo auth status
qbo company info --sandbox --json
If you get "no company ID", set one: export QBO_COMPANY_ID=<realm_id> or qbo company switch <realm_id>.
Troubleshooting
| Problem | Fix |
|---|
no company ID | export QBO_COMPANY_ID=<realm> or qbo company switch <realm> |
ApplicationAuthorizationFailed on production endpoint | Use --sandbox or re-authorize the app for a production company |
| OAuth state mismatch | Restart qbo auth login and use the newly generated URL |
| Token expired | qbo auth refresh or re-run qbo auth login |
Response Structure
QBO wraps all responses. Know the shapes:
list/query returns {"QueryResponse": {"Invoice": [...], "startPosition": 1, ...}}.
Use --results-only to unwrap to just the array.
get returns {"Invoice": {...}}. Use jq to drill in: qbo get invoice 123 --json | jq '.Invoice'
create/update returns the same wrapper as get.
report returns {"Header": {...}, "Rows": {...}}.
Always use --json when parsing output programmatically.
Common Patterns
qbo list customers --sandbox --json --results-only
qbo list invoices --where "Balance > '0'" --sandbox --json --results-only
qbo get invoice 145 --sandbox --json | jq '.Invoice'
qbo get customer 58 --sandbox --json | jq '.Customer | {Id, DisplayName, Balance}'
echo '{"DisplayName":"Acme Corp"}' | qbo create customer -f - --sandbox --json
qbo create invoice -f invoice.json --sandbox --json
echo '{"CustomerRef":{"value":"58"},"TotalAmt":500,"Line":[{"Amount":500,"LinkedTxn":[{"TxnId":"145","TxnType":"Invoice"}]}]}' | qbo create payment -f - --sandbox --json
echo '{"Id":"58","SyncToken":"0","DisplayName":"New Name"}' | qbo update customer 58 --sparse -f - --sandbox --json
qbo report profit-and-loss --start-date 2026-01-01 --end-date 2026-12-31 --sandbox --json
qbo report balance-sheet --sandbox --json
qbo query "SELECT Id, DisplayName, Balance FROM Customer WHERE Active = true" --sandbox --json --results-only
Workflow: Create -> Invoice -> Payment
echo '{"DisplayName":"New Client","PrimaryEmailAddr":{"Address":"client@example.com"}}' \
| qbo create customer -f - --sandbox --json | jq '.Customer.Id'
echo '{"Name":"Consulting","Type":"Service","IncomeAccountRef":{"value":"1"},"UnitPrice":150}' \
| qbo create item -f - --sandbox --json | jq '.Item.Id'
echo '{"CustomerRef":{"value":"CUSTOMER_ID"},"Line":[{"Amount":1500,"DetailType":"SalesItemLineDetail","SalesItemLineDetail":{"ItemRef":{"value":"ITEM_ID"},"Qty":10,"UnitPrice":150}}]}' \
| qbo create invoice -f - --sandbox --json | jq '.Invoice | {Id, DocNumber, TotalAmt}'
echo '{"CustomerRef":{"value":"CUSTOMER_ID"},"TotalAmt":1500,"Line":[{"Amount":1500,"LinkedTxn":[{"TxnId":"INVOICE_ID","TxnType":"Invoice"}]}]}' \
| qbo create payment -f - --sandbox --json
Attachments
qbo attach invoice 123 receipt.pdf --json
qbo list attachable --where "AttachableRef.EntityRef.value = '123'" --json --results-only
qbo download <id>
Recurring Transactions
Templates that auto-post transactions on a schedule (e.g. monthly amortization).
Each record wraps an underlying transaction by type with a nested RecurringInfo
block: {"JournalEntry": {"RecurringInfo": {...}, "Line": [...]}}.
qbo list recurringtransaction --json --results-only
qbo get recurringtransaction <id> --json
qbo create recurringtransaction -f examples/recurring-je.json --json
qbo delete recurringtransaction <id>
Agent Introspection
qbo schema --json
qbo schema get --json
qbo exit-codes --json
Exit Codes
0=success, 1=error, 2=usage, 3=empty, 4=auth required, 5=not found, 6=forbidden, 7=rate limited, 8=retryable, 10=config error.
Reference
See references/COMMANDS.md for full command reference.