Reference
QuickBooks
Set up QuickBooks OAuth for accounting and financial management
Overview
Section titled “Overview”Connect your users to QuickBooks for accounting data, invoicing, expense tracking, and financial reporting.
| Property | Value |
|---|---|
| Provider ID | quickbooks |
| Category | Finance |
| PKCE | Supported (S256 — Alter sends it; Intuit accepts it but does not document PKCE) |
| Token refresh | Automatic |
| Redirect URI | Shown in Developer Portal |
Step 1: Create a QuickBooks OAuth App
Section titled “Step 1: Create a QuickBooks OAuth App”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.
Step 2: Add to Alter Vault
Section titled “Step 2: Add to Alter Vault”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.
Available Scopes
Section titled “Available Scopes”| Scope | Description |
|---|---|
com.intuit.quickbooks.accounting | Access to QuickBooks accounting data |
com.intuit.quickbooks.payment | Access to QuickBooks payment data |
openid | Access to OpenID Connect identity |
profile | Access to user profile information |
email | Access to user email address |
Company identity (realmId)
Section titled “Company identity (realmId)”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 / accountIdentifier | The realmId, as Intuit sent it (a numeric string) |
account_display_name / accountDisplayName | The 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_identifierif 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.
When the company id is null
Section titled “When the company id is null”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.accountingscope 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.
Policy-cataloged operations
Section titled “Policy-cataloged operations”Alter policy rules can target these attested operations and families for operation-level and parameter-aware controls.
| Operation ID | Families | Method | Provider path |
|---|---|---|---|
accounts/create | write | POST | /v3/company/{realmId}/account |
accounts/get | read | GET | /v3/company/{realmId}/account/{accountId} |
batch | delete, payment, read, write | POST | /v3/company/{realmId}/batch |
bills/create | write | POST | /v3/company/{realmId}/bill |
bills/get | read | GET | /v3/company/{realmId}/bill/{billId} |
companyinfo/get | read | GET | /v3/company/{realmId}/companyinfo/{companyInfoId} |
customers/create | write | POST | /v3/company/{realmId}/customer |
customers/get | read | GET | /v3/company/{realmId}/customer/{customerId} |
invoices/create | write | POST | /v3/company/{realmId}/invoice |
invoices/get | read | GET | /v3/company/{realmId}/invoice/{invoiceId} |
invoices/send | send | POST | /v3/company/{realmId}/invoice/{invoiceId}/send |
items/create | write | POST | /v3/company/{realmId}/item |
items/get | read | GET | /v3/company/{realmId}/item/{itemId} |
payments/create | payment, write | POST | /v3/company/{realmId}/payment |
payments/get | read | GET | /v3/company/{realmId}/payment/{paymentId} |
preferences/get | read | GET | /v3/company/{realmId}/preferences |
query | read | GET | /v3/company/{realmId}/query |
reports/balance-sheet | read | GET | /v3/company/{realmId}/reports/BalanceSheet |
reports/profit-and-loss | read | GET | /v3/company/{realmId}/reports/ProfitAndLoss |
vendors/create | write | POST | /v3/company/{realmId}/vendor |
vendors/get | read | GET | /v3/company/{realmId}/vendor/{vendorId} |