Skip to content

Reference

QuickBooks

Set up QuickBooks OAuth for accounting and financial management

Connect your users to QuickBooks for accounting data, invoicing, expense tracking, and financial reporting.

PropertyValue
Provider IDquickbooks
CategoryFinance
PKCESupported (S256 — Alter sends it; Intuit accepts it but does not document PKCE)
Token refreshAutomatic
Redirect URIShown in Developer Portal

Go to the Intuit Developer Dashboard

Navigate to developer.intuit.com/app/developer/dashboard and sign in (or select My Hub > App dashboard).

Create a new app

Select the app card with a + and follow the on-screen steps. When asked for scopes, select Accounting (add Payments if the application needs the Payments API).

Configure the redirect URI

Open the app, select Settings in the left navigation, then the Redirect URIs tab. Select Add URI and paste the Alter callback URL from the Developer Portal. Add it under both Development and Production if both key sets will be used.

Get credentials

Select Keys and credentials, choose Development (sandbox companies only) or Production, turn on Show credentials, and copy the Client ID and Client Secret. Production credentials appear only after Intuit approves the Production Key questionnaire.

Open the Developer Portal

Go to portal.alterauth.com and navigate to the application.

Add QuickBooks provider

Go to OAuth Providers > Add Provider > QuickBooks.

Enter credentials

  • Client ID: Paste your QuickBooks Client ID
  • Client Secret: Paste your QuickBooks Client Secret

Select scopes

Choose the scopes the application needs.

Save

Click Save. The provider is now active.

ScopeDescription
com.intuit.quickbooks.accountingAccess to QuickBooks accounting data
com.intuit.quickbooks.paymentAccess to QuickBooks payment data
openidAccess to OpenID Connect identity
profileAccess to user profile information
emailAccess to user email address

Every QuickBooks Online Accounting URL carries the connected company’s id — Intuit calls it the realmId — as in /v3/company/{realmId}/preferences. Intuit reliably returns it only on the OAuth redirect (its OpenID ID-token claim is optional and populated only when the user is signed in to QuickBooks), so Alter captures it at connect time, checks with the newly issued token that the company’s CompanyInfo is readable, and exposes it on the grant:

Grant field (Python / TypeScript)Value
account_identifier / accountIdentifierThe realmId, as Intuit sent it (a numeric string)
account_display_name / accountDisplayNameThe company name

Both are the grant’s general-purpose account fields, not QuickBooks-specific ones: the same account_identifier holds an email address for most providers and an account id for others, so read it per provider rather than assuming a shape. See the Python and TypeScript type reference. Either field can be null — see below.

Build API URLs from the grant:

page = await app.list_grants(end_user_token=end_user_token, provider_id="quickbooks")
# Rows are a union of connection kinds; keep the OAuth ones.
grants = [g for g in page.grants if g.grant_kind == "oauth"]
if not grants:
raise RuntimeError("No QuickBooks connection yet — run the Connect flow first.")
qb = grants[0]
realm_id = qb.account_identifier
if realm_id is None:
# Not an error state: see "when the company id is null" below.
raise RuntimeError("Company id not captured — ask the user to reconnect.")
response = await app.request(
"GET",
f"https://quickbooks.api.intuit.com/v3/company/{realm_id}/preferences",
grant_id=qb.grant_id,
)
const page = await app.listGrants({ endUserToken, providerId: "quickbooks" });
// Rows are a union of connection kinds; keep the OAuth ones.
const grants = page.grants.filter((g) => g.grantKind === "oauth");
const qb = grants[0];
if (!qb) {
throw new Error("No QuickBooks connection yet — run the Connect flow first.");
}
const realmId = qb.accountIdentifier;
if (realmId === null) {
// Not an error state: see "when the company id is null" below.
throw new Error("Company id not captured — ask the user to reconnect.");
}
const response = await app.request(
"GET",
`https://quickbooks.api.intuit.com/v3/company/${realmId}/preferences`,
{ grantId: qb.grantId },
);

The company id is confirmed through the Accounting API, so the com.intuit.quickbooks.accounting scope is always requested and cannot be removed from a connection.

A null company id means Alter could not confirm the company identity when the connection was established. The token can still be active: connection health and company identification are separate facts.

This can affect older connections or connections whose CompanyInfo confirmation failed. After confirmation is available, reconnect from the wallet to populate the verified company id on the existing connection. A fresh Connect flow can create a separate credential when the old credential has no verified subject.

If another active credential already owns the same deduplication subject, wallet reconnect preserves that ownership instead of merging credentials. It can still refresh the original connection’s token, company id and name; only the colliding internal subject backfill is skipped.

Treat account_identifier as nullable at every call site. Show a reconnect prompt when it is null instead of building an Accounting URL from it. Connect polling and browser callbacks return null, never a manufactured "Unknown" identifier.

Sandbox (Development-key) companies are confirmed against the sandbox Accounting host. Alter tries its production and sandbox candidates and uses the first valid authenticated CompanyInfo response. Use that company’s corresponding Accounting host for your calls.

The identifier is the verified callback realmId. CompanyInfo.Id is the CompanyInfo object’s ID (Intuit’s example is "1"), not the company realm. To read CompanyInfo yourself, Intuit’s path repeats the realm — /v3/company/{realmId}/companyinfo/{realmId} — so the {companyInfoId} placeholder in the operations table below is also the realmId. The optional company display name comes from CompanyInfo.CompanyName. See the CompanyInfo reference.

  • QuickBooks has separate sandbox and production environments. Use sandbox credentials during development.
  • The com.intuit.quickbooks.accounting scope is required for every Accounting API call; Alter always requests it (see Company identity above).
  • See the QuickBooks OAuth 2.0 documentation for more details.

Alter policy rules can target these attested operations and families for operation-level and parameter-aware controls.

Operation IDFamiliesMethodProvider path
accounts/createwritePOST/v3/company/{realmId}/account
accounts/getreadGET/v3/company/{realmId}/account/{accountId}
batchdelete, payment, read, writePOST/v3/company/{realmId}/batch
bills/createwritePOST/v3/company/{realmId}/bill
bills/getreadGET/v3/company/{realmId}/bill/{billId}
companyinfo/getreadGET/v3/company/{realmId}/companyinfo/{companyInfoId}
customers/createwritePOST/v3/company/{realmId}/customer
customers/getreadGET/v3/company/{realmId}/customer/{customerId}
invoices/createwritePOST/v3/company/{realmId}/invoice
invoices/getreadGET/v3/company/{realmId}/invoice/{invoiceId}
invoices/sendsendPOST/v3/company/{realmId}/invoice/{invoiceId}/send
items/createwritePOST/v3/company/{realmId}/item
items/getreadGET/v3/company/{realmId}/item/{itemId}
payments/createpayment, writePOST/v3/company/{realmId}/payment
payments/getreadGET/v3/company/{realmId}/payment/{paymentId}
preferences/getreadGET/v3/company/{realmId}/preferences
queryreadGET/v3/company/{realmId}/query
reports/balance-sheetreadGET/v3/company/{realmId}/reports/BalanceSheet
reports/profit-and-lossreadGET/v3/company/{realmId}/reports/ProfitAndLoss
vendors/createwritePOST/v3/company/{realmId}/vendor
vendors/getreadGET/v3/company/{realmId}/vendor/{vendorId}

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.