SuperAPI AI Directory

qbo

Office & productivity

View repository

Updated 10-15 23:57

About this skill

qbo — QuickBooks Online CLI

Install

# macOS / Linux
brew install voska/tap/qbo

# Scoop (Windows)
scoop bucket add voska https://github.com/voska/scoop-bucket && scoop install qbo

# Go (any platform)
go install github.com/voska/qbo-cli/cmd/qbo@latest

# Binary: https://github.com/voska/qbo-cli/releases

Getting Credentials

Requires an Intuit Developer account and a QuickBooks app for OAuth credentials.

  1. Sign up at https://developer.intuit.com and create an app from the dashboard.
  2. Select QuickBooks Online and Payments as the platform.
  3. Under Keys & credentials, find your Client ID and Client Secret.
  4. 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:

  1. 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.
  2. Login on the same machine — If the agent runs on a machine with a browser, use a tunnel so localhost callbacks work.
  3. 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

# Store app-level OAuth client credentials in the system keychain (one time).
# After this, no QBO_CLIENT_ID/SECRET env vars are needed for any command.
qbo auth set-client --client-id your_client_id --client-secret your_client_secret

# Sandbox — uses localhost callback automatically
qbo auth login --sandbox

# Production — specify your redirect URI
qbo auth login --redirect-uri https://yourdomain.com

# Or set it as env var / config so you don't need the flag every time
export QBO_REDIRECT_URI=https://yourdomain.com
qbo auth login

# Print the URL without opening a browser (useful for agents/remote)
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/void returns the same wrapper as get.
  • report returns {"Header": {...}, "Rows": {...}}.

Always use --json when parsing output programmatically.

Common Patterns

# List with filtering — --results-only gives you the array directly
qbo list customers --sandbox --json --results-only
qbo list invoices --where "Balance > '0'" --sandbox --json --results-only

# Get by ID — drill into entity key with jq
qbo get invoice 145 --sandbox --json | jq '.Invoice'
qbo get customer 58 --sandbox --json | jq '.Customer | {Id, DisplayName, Balance}'

# Create from stdin
echo '{"DisplayName":"Acme Corp"}' | qbo create customer -f - --sandbox --json

# Create from file
qbo create invoice -f invoice.json --sandbox --json

# Record a payment against an invoice
echo '{"CustomerRef":{"value":"58"},"TotalAmt":500,"Line":[{"Amount":500,"LinkedTxn":[{"TxnId":"145","TxnType":"Invoice"}]}]}' | qbo create payment -f - --sandbox --json

# Sparse update (partial)
echo '{"Id":"58","SyncToken":"0","DisplayName":"New Name"}' | qbo update customer 58 --sparse -f - --sandbox --json

# Reports
qbo report profit-and-loss --start-date 2026-01-01 --end-date 2026-12-31 --sandbox --json
qbo report balance-sheet --sandbox --json

# Raw query
qbo query "SELECT Id, DisplayName, Balance FROM Customer WHERE Active = true" --sandbox --json --results-only

Workflow: Create -> Invoice -> Payment

# 1. Create customer
echo '{"DisplayName":"New Client","PrimaryEmailAddr":{"Address":"client@example.com"}}' \
  | qbo create customer -f - --sandbox --json | jq '.Customer.Id'

# 2. Create item (service)
echo '{"Name":"Consulting","Type":"Service","IncomeAccountRef":{"value":"1"},"UnitPrice":150}' \
  | qbo create item -f - --sandbox --json | jq '.Item.Id'

# 3. Create invoice (use IDs from above)
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}'

# 4. Record payment
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>

Transaction PDFs

qbo pdf invoice 123 -o invoice-123.pdf   # also estimate, salesreceipt, creditmemo, purchaseorder

Voiding Transactions

Void Invoice, Payment, SalesReceipt, or BillPayment to zero amounts while keeping the document number, date, and audit trail. A void can't be undone.

qbo void invoice 145 --dry-run
qbo void invoice 145 --force --no-input --sandbox --json
qbo void payment 200 --sync-token 3 --force --no-input --sandbox --json

The command reads the current SyncToken before posting; --sync-token pins the expected value and fails without posting on a mismatch. Already-void records (TotalAmt zero and PrivateNote starting with Voided) return with a hint and no POST. Dry runs make no API calls; a pinned token includes the exact body. Use --force with --no-input; otherwise the command prompts for confirmation.

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>   # reads + echoes the full object for you

Agent Introspection

qbo schema --json             # Full CLI tree, all entities, all flags
qbo schema get --json         # Schema for a specific command
qbo schema void --json        # Void arguments and --sync-token flag
qbo exit-codes --json         # Exit codes as 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.