fin/accounting/quickbooks

QUICKBOOKS

QuickBooks Online (QBO) AI integration expert.

production Any HTTP client, Node.js, Python, Cloudflare Workers
requires: fin/modelling
improves: fin/accounting

QuickBooks Online AI Integration

QuickBooks Online (QBO) is the dominant cloud accounting platform for SMBs. It exposes a REST API v3 for programmatic access to the full accounting ledger — customers, vendors, invoices, bills, payments, accounts, reports, and more.

Docs: https://developer.intuit.com/app/developer/qbo/docs/develop

The 2nth Model: One Person + One AI

RoleThe Human DecidesThe AI Enables
Business OwnerStrategy, pricing, supplier termsCash position summary, overdue receivables alerts, P&L narrative
BookkeeperException review, chart of accounts structureAuto-categorisation, bank rec suggestions, data entry from documents
AccountantTax strategy, audit, advisoryReport generation, variance analysis, client-ready narratives
Finance ManagerBudget sign-off, forecasting assumptionsActuals vs budget, scenario modelling from live QBO data
Accounts ReceivableEscalations, dispute resolutionAged AR report, draft payment reminders, invoice status lookup
Accounts PayablePayment approvals, vendor relationshipsBill schedule, cash flow impact, duplicate invoice detection

Authentication

QBO uses OAuth 2.0 Authorization Code Flow with a 1-hour access token and 100-day rolling refresh token.

# Environment variables
QBO_CLIENT_ID="ABxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
QBO_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
QBO_REDIRECT_URI="https://yourapp.com/callback"
QBO_REALM_ID="123456789"          # Company ID — obtained from token response
QBO_ACCESS_TOKEN="eyJ..."         # Refresh every 60 min
QBO_REFRESH_TOKEN="AB11..."       # Rotate every 100 days
QBO_SANDBOX=false                 # true for development

OAuth Endpoints

Authorization:  https://appcenter.intuit.com/connect/oauth2
Token:          https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer

Scopes

com.intuit.quickbooks.accounting    # All accounting data (required)
com.intuit.quickbooks.payment       # Payment processing

Token Refresh

curl -X POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer \
  -H "Authorization: Basic base64(CLIENT_ID:CLIENT_SECRET)" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token&refresh_token=AB11..."
# Returns: new access_token + new refresh_token (rolling window resets)

Base URL

Production: https://quickbooks.api.intuit.com/v3/company/{realmId}/
Sandbox:    https://sandbox-quickbooks.api.intuit.com/v3/company/{realmId}/

Always include:

Accept: application/json
Content-Type: application/json
Authorization: Bearer {access_token}

Query Language

QBO uses a SQL-like query syntax (not standard SQL — it's Intuit's own dialect).

GET /v3/company/{realmId}/query?query=SELECT * FROM Invoice WHERE Balance > '0'
-- Key syntax rules
SELECT * FROM Invoice WHERE TotalAmt > '1000.0'          -- single quotes required
SELECT * FROM Customer WHERE DisplayName LIKE 'Acme%'    -- % wildcard only
SELECT * FROM Invoice WHERE Balance > '0' ORDER BY DueDate DESC
SELECT * FROM Invoice STARTPOSITION 1 MAXRESULTS 200     -- max 1000 per page

-- Paginate
SELECT COUNT(*) FROM Invoice                              -- get total first
SELECT * FROM Invoice STARTPOSITION 201 MAXRESULTS 200   -- next page

Critical: values in WHERE clauses must use single quotes. WHERE TotalAmt > 1000 fails silently or errors.

Always pin the API version:

?minorversion=73    # Append to all requests — defaults to 2014 without it

Core Entities

Invoice

{
  "CustomerRef": { "value": "42", "name": "Acme Corp" },
  "TxnDate": "2026-04-01",
  "DueDate": "2026-04-30",
  "Line": [
    {
      "Amount": 500.00,
      "DetailType": "SalesItemLineDetail",
      "SalesItemLineDetail": {
        "ItemRef": { "value": "1", "name": "Consulting" },
        "Qty": 5,
        "UnitPrice": 100.00
      }
    }
  ]
}

Create: POST /v3/company/{realmId}/invoice Read: GET /v3/company/{realmId}/invoice/{id} Update: POST /v3/company/{realmId}/invoice (include Id + SyncToken) Send: POST /v3/company/{realmId}/invoice/{id}/send?sendTo=client@example.com

Payment (apply to invoice)

{
  "TotalAmt": 500.00,
  "CustomerRef": { "value": "42" },
  "Line": [{
    "Amount": 500.00,
    "LinkedTxn": [{ "TxnId": "123", "TxnType": "Invoice" }]
  }]
}

Bill (accounts payable)

{
  "VendorRef": { "value": "56" },
  "TxnDate": "2026-04-01",
  "DueDate": "2026-04-30",
  "Line": [{
    "Amount": 200.00,
    "DetailType": "AccountBasedExpenseLineDetail",
    "AccountBasedExpenseLineDetail": {
      "AccountRef": { "value": "7", "name": "Office Supplies" }
    }
  }]
}

Customer

{
  "DisplayName": "Acme Corp",
  "CompanyName": "Acme Corporation",
  "PrimaryEmailAddr": { "Address": "accounts@acme.com" },
  "BillAddr": { "Line1": "123 Main St", "City": "Cape Town", "CountrySubDivisionCode": "WC" },
  "DefaultTaxCodeRef": { "value": "TAX" }
}

Account (Chart of Accounts)

AccountType values: Bank | Accounts Receivable | Other Current Asset | Fixed Asset |
                    Accounts Payable | Credit Card | Equity | Income | Expense | Cost of Goods Sold

Key MCP Tools

ToolDescription
qbo_queryRun SQL-like query against any QBO entity
qbo_get_invoiceFetch single invoice with full line item detail
qbo_create_invoiceCreate invoice for a customer
qbo_send_invoiceEmail invoice to customer via QBO
qbo_apply_paymentApply payment against one or more invoices
qbo_get_customerCustomer detail including balance and credit limit
qbo_list_overdueInvoices past due date with days overdue
qbo_aged_receivablesAR aging report (Current / 1-30 / 31-60 / 61-90 / 90+)
qbo_profit_lossP&L report for a date range
qbo_balance_sheetBalance sheet as at a date
qbo_cash_flowCash flow statement
qbo_cdcChange data capture — entities modified since a timestamp
qbo_batchSubmit up to 30 operations in one request

Reports API

Reports are not entities — they use a separate endpoint pattern:

# Profit & Loss
GET /v3/company/{realmId}/reports/ProfitAndLoss?start_date=2026-01-01&end_date=2026-03-31

# Balance Sheet
GET /v3/company/{realmId}/reports/BalanceSheet?date_macro=This%20Year-to-date

# Aged Receivables
GET /v3/company/{realmId}/reports/AgedReceivableDetail

# Cash Flow
GET /v3/company/{realmId}/reports/CashFlow?start_date=2026-01-01&end_date=2026-03-31

# Transaction List (detailed ledger)
GET /v3/company/{realmId}/reports/TransactionList?start_date=2026-01-01&end_date=2026-03-31

Reports have a 400,000-cell limit. For large date ranges, break into quarterly or monthly requests and concatenate.

Change Data Capture (CDC)

CDC returns all objects of given entity types modified since a timestamp — far more efficient than polling.

GET /v3/company/{realmId}/cdc?entities=Invoice,Payment,Customer&changedSince=2026-04-01T00:00:00Z

Returns a CDCResponse with QueryResponse arrays per entity — including deleted records (status: "Deleted").

CDC + Webhooks pattern (recommended):

Webhooks

Subscribe at: https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks

// Payload arrives as POST to your endpoint
{
  "eventNotifications": [{
    "realmId": "123456789",
    "dataChangeEvent": {
      "entities": [{
        "name": "Invoice",
        "id": "456",
        "operation": "Update",
        "lastUpdated": "2026-04-01T10:30:00Z"
      }]
    }
  }]
}

Verify the intuit-signature header (HMAC-SHA256 of body with your verifier token):

const sig = request.headers.get('intuit-signature');
const body = await request.text();
const expected = btoa(
  String.fromCharCode(...new Uint8Array(
    await crypto.subtle.sign('HMAC', verifierKey, new TextEncoder().encode(body))
  ))
);
if (sig !== expected) return new Response('Unauthorized', { status: 401 });

Rate Limits

Endpoint typeLimit
Standard500 req/min per realm
Batch40 req/min per realm
Reports200 req/min per realm
Concurrent10 simultaneous per realm+app

HTTP 429 on breach — implement exponential backoff. Start at 1s, double up to 60s.

Pricing (as of 2025)

Intuit introduced metered pricing for the App Partner Program:

TierMonthlyGET credits included
Builder (free)$0500K
Silver$3001M
Gold$1,70010M
Platinum$4,50075M

Design integrations to minimise unnecessary GET calls — use CDC instead of polling, cache reference data (customers, items, accounts).

Batch Operations

Up to 30 operations per batch — useful for bulk imports:

POST /v3/company/{realmId}/batch
{
  "BatchItemRequest": [
    {
      "bId": "1",
      "operation": "create",
      "Invoice": { ...invoice object... }
    },
    {
      "bId": "2",
      "operation": "update",
      "Customer": { "Id": "42", "SyncToken": "5", "DisplayName": "Acme Corp Updated" }
    }
  ]
}

Common Gotchas

See Also