QuickBooks Online connector setup

Operator·5 minutes to read

This page is the setup half — creating the app Intuit needs on its developer portal and getting its client id and secret into this deployment. Authorising a connection afterwards is the Authenticate control on the plugin sheet, which is what actually sends a member to Intuit. What a member can then do is docs/using/connectors/quickbooks.md. The facts behind every step are in docs/research/connectors/quickbooks.md (read 2026-10-21).

1. Create the app on the Intuit Developer portal

  1. Sign in at the Intuit Developer portal with an Intuit account and open Dashboard → Create an app. Pick QuickBooks Online and Payments and, for scopes, only Accounting (com.intuit.quickbooks.accounting). Payments is not part of this connector.

  2. Open the app's Keys & credentials. Development and Production are separate tabs, each with its own client id, secret and redirect URIs.

  3. Under Redirect URIs, add the callback URL from docs/connectors/README.md — with the quickstart's default BOTSEON_PUBLIC_URL, that is:

    http://127.0.0.1:3000/api/oauth/quickbooks/callback
    

    It must match exactly. Intuit accepts http://localhost only on the Development tab; production redirect URIs must be https:.

  4. Copy the Client ID and Client Secret of the tab you will use.

Development keys reach only sandbox companies (every Intuit Developer account comes with one). To connect real books, complete the app's production settings — its details, the Reconnect URL Intuit now requires, and Intuit's app questionnaire — and use the Production tab's keys.

2. Scopes

One: com.intuit.quickbooks.accounting. Intuit has no read-only accounting scope — the one scope covers reading and changing the books alike — and its token response names no scope at all, so the plugin sheet shows one sentence for the grant rather than a scope list: QuickBooks grants all or nothing, so this account can read and change everything the connected QuickBooks user can. The separation lives in the tools instead: the one tool that writes raises an approval card first. What the connected user can see is still bounded by their own QuickBooks role.

3. Enter the app

One command, with the client secret on standard input — there is no flag that takes it and no environment variable that holds it (see docs/connectors/README.md):

printf %s "$CLIENT_SECRET" | botseon oauth-app set \
  --org <organisation id> --slug quickbooks \
  --client-id <the client id from step 1> \
  --auth-url https://appcenter.intuit.com/connect/oauth2 \
  --token-url https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer \
  --revoke-url https://developer.api.intuit.com/v2/oauth2/tokens/revoke \
  --configured-by <your user id>

The three URLs are Intuit's discovery document's own (authorization_endpoint, token_endpoint, revocation_endpoint); they are the same for development and production keys. With the revoke URL set, a member's Remove revokes the grant at Intuit too.

It stores the secret encrypted and prints the redirect URL from step 1 back, so you can check the two match. botseon oauth-app show --org <organisation id> --slug quickbooks says whether an app is configured and under which client id; nothing prints the secret back.

There is no company id to enter. Intuit names the company a member picked on its redirect back (realmId), and the callback stores it on that member's account; a return without one is refused rather than stored half-connected. A member on development keys sets the plugin's QuickBooks environment to sandbox; everyone else leaves it empty.

Never put the client secret in a bot's description, a memory, or anywhere else a model reads as ordinary text.

4. What this connector does and does not do

  • quickbooks.query — runs one Intuit query (SELECT … FROM <Entity>). It must state MAXRESULTS n, at most 200, so a call reads one page; STARTPOSITION pages on.
  • quickbooks.get_entity — one invoice, bill, customer, vendor, payment, account, item (and the other transaction entities) by id.
  • quickbooks.company_info — the company's name, legal name, country, address and fiscal year start.
  • quickbooks.report — profit and loss, balance sheet, cash flow, aged receivables or aged payables for a period (the aged two on a day), accrual or cash basis, flattened to at most 500 lines.
  • quickbooks.create_invoice — one invoice for a customer, with item lines. Tagged write; the approval card names the customer. Emailing it is left to QuickBooks: an emailing tool would be this connector's first external_send, and it is not in this release.

The connector sends minorversion=75 on every call: Intuit retired minor versions 1–74 on 2025-08-01. Intuit throttles each company (a 429 pauses the bot's calls and retries, after the answer's Retry-After or else 60 seconds). An app in the Builder tier of Intuit's App Partner Program gets 500,000 CorePlus calls per workspace per month and is answered 429 after that: one app serves every member who connects through it, so check the app's usage in the Intuit Developer portal if every member's calls pause at once. Payments, payroll, bank feeds and the change-data-capture feed are not part of this connector.

5. Tokens

Access tokens last an hour and are refreshed on use with the client id and secret as HTTP Basic. Intuit rotates the refresh token — each refresh can return a new one and the old one stops working once it has — and the token store keeps whichever the last refresh returned. A refresh token is valid while used at least every 100 days, and at most five years from issue; past that, the member is asked to Authenticate again.

6. The nightly live test

docs/operations/connector-live-setup/quickbooks.md is the checklist for the sandbox company and the secrets the connector-live workflow's QuickBooks case (packages/connectors/src/quickbooks.live.test.ts) reads. Without them it skips, and a skip never counts as proved.

Last verified against build 1abb720.