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
-
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. -
Open the app's Keys & credentials. Development and Production are separate tabs, each with its own client id, secret and redirect URIs.
-
Under Redirect URIs, add the callback URL from
docs/connectors/README.md— with the quickstart's defaultBOTSEON_PUBLIC_URL, that is:http://127.0.0.1:3000/api/oauth/quickbooks/callbackIt must match exactly. Intuit accepts
http://localhostonly on the Development tab; production redirect URIs must behttps:. -
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 stateMAXRESULTS n, at most 200, so a call reads one page;STARTPOSITIONpages 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. Taggedwrite; the approval card names the customer. Emailing it is left to QuickBooks: an emailing tool would be this connector's firstexternal_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.