Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

QuickBooks

Connect FlutterFlow to QuickBooks Online using a FlutterFlow API Call pointed at a Firebase or Supabase proxy that handles OAuth 2.0 and rotating refresh tokens. Two QuickBooks-specific details drive the whole build: every API request requires the company realmId embedded in the URL path, and OAuth refresh tokens rotate on each use — so the proxy must persist the newest refresh token after every call or the connection silently breaks.

What you'll learn

  • How to create an Intuit developer app and obtain a client ID and client secret for QuickBooks Online
  • How to deploy a Firebase or Supabase proxy that runs the OAuth Authorization Code flow and handles rotating refresh-token persistence
  • Why the company realmId is required in every QuickBooks API URL path and how to capture and store it per connected company
  • How to configure a FlutterFlow API Group pointing at your proxy to fetch invoices, expenses, and balances
  • How to parse QuickBooks nested response objects into flat FlutterFlow Data Types and bind them to a dashboard
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced18 min read2-4 hoursFinance & AccountingLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to QuickBooks Online using a FlutterFlow API Call pointed at a Firebase or Supabase proxy that handles OAuth 2.0 and rotating refresh tokens. Two QuickBooks-specific details drive the whole build: every API request requires the company realmId embedded in the URL path, and OAuth refresh tokens rotate on each use — so the proxy must persist the newest refresh token after every call or the connection silently breaks.

Quick facts about this guide
FactValue
ToolQuickBooks
CategoryFinance & Accounting
MethodFlutterFlow API Call
DifficultyAdvanced
Time required2-4 hours
Last updatedJuly 2026

Why QuickBooks Needs a Proxy — Two Details That Break Most Builds

QuickBooks Online's API is well-documented and official, but it introduces two requirements that surprise most FlutterFlow developers and break integrations if not handled correctly from the start. The first is the OAuth client secret: QuickBooks uses the Authorization Code grant flow, which requires exchanging an authorization code for tokens using your client secret. That secret must never appear in your Flutter app bundle — if a user decompiles your APK or IPA, they get full accounting access to every QuickBooks company that has connected to your app. The client secret lives in a Firebase Cloud Function or Supabase Edge Function, and the FlutterFlow app only ever talks to your proxy.

The second detail is refresh-token rotation. Every time your proxy exchanges a QuickBooks refresh token for a new access token, Intuit issues a brand new refresh token and immediately invalidates the previous one. If your proxy fetches a new access token but does not immediately write the new refresh token back to persistent storage, the next token refresh will fail with a 401 because the old token is already gone. This is a silent failure: the integration appears to work for the first token lifetime (~60 days for access tokens, ~100 days for refresh tokens) and then stops working after the first rotation event. The fix is simple — always write the new refresh_token to Firestore or Supabase immediately after receiving it — but you must know about it before you start building.

The third QuickBooks-specific requirement is the company realmId. Every API request to QuickBooks Online must include the company's realmId directly in the URL path: /v3/company/{realmId}/invoice, /v3/company/{realmId}/account, and so on. The realmId is the numeric ID of the specific QuickBooks company you are querying, and it is different for every customer. You capture it from the OAuth callback redirect URL and store it per connected company in your backend. Pass it to your proxy as a request variable so one proxy instance can serve multiple connected companies.

Integration method

FlutterFlow API Call

FlutterFlow API Calls are configured to hit a Firebase Cloud Function or Supabase Edge Function proxy that you deploy and maintain. The proxy runs the QuickBooks OAuth 2.0 Authorization Code flow, persists and rotates refresh tokens in Firestore or Supabase storage, and proxies every QuickBooks REST request on behalf of the app. The FlutterFlow app never holds the client secret, access token, or refresh token — it only calls your proxy.

Prerequisites

  • A QuickBooks Online account — a free Sandbox account is sufficient for development; a paid QuickBooks plan is required for live company data
  • An Intuit Developer account at developer.intuit.com with a QuickBooks Online app created to get client ID and client secret
  • A Firebase project with Cloud Functions on the Blaze plan OR a Supabase project with Edge Functions enabled
  • A FlutterFlow project on a paid plan (API Calls require Starter or above)
  • Basic understanding of OAuth 2.0 Authorization Code flow and how to deploy serverless functions

Step-by-step guide

1

Step 1: Create an Intuit developer app and configure OAuth settings

Go to developer.intuit.com and sign in with your Intuit account. Click the Dashboard tab, then click Create an App. Select QuickBooks Online and Payments as the platform. Name your app, then select the scopes you need: com.intuit.quickbooks.accounting covers invoices, expenses, accounts, and most of what a dashboard needs. Once the app is created, open its Keys & Credentials tab. You will see a Client ID and Client Secret for both Sandbox and Production environments. The Sandbox client ID and secret connect to Intuit's free Sandbox environment — use these during all development so you never affect real accounting data. Paste the Sandbox client ID and client secret somewhere safe — you will need them in Step 2 when deploying the proxy. Never paste them into FlutterFlow. In the Redirect URIs section, add the redirect URI that your proxy's OAuth callback will use. If you are using a Firebase Cloud Function, this might be something like https://us-central1-YOUR-PROJECT-ID.cloudfunctions.net/quickbooksCallback. Intuit requires this URI to be registered before the OAuth flow will work — a mismatch returns an 'invalid_request' error from Intuit's auth server. Also note the Sandbox base URL for QuickBooks API requests: sandbox-quickbooks.api.intuit.com. You will use this in the proxy during development and switch to quickbooks.api.intuit.com for production.

Pro tip: Start in Sandbox mode. Intuit's Sandbox comes with pre-populated sample data — invoices, customers, expenses — that you can use to test your integration without needing a real QuickBooks company account.

Expected result: You have a Sandbox client ID, client secret, and a registered redirect URI in your Intuit developer app. The app's scopes include com.intuit.quickbooks.accounting.

2

Step 2: Deploy a proxy that runs the OAuth flow and persists rotating refresh tokens

Your Firebase Cloud Function (or Supabase Edge Function) needs to do three things: (1) redirect the user to Intuit's authorization URL so they can approve the connection, (2) receive the OAuth callback with the authorization code and exchange it for an access token and refresh token, and (3) on every subsequent API request, use the stored access token and refresh it when it expires — always writing the new refresh token back to storage immediately. The Authorization URL you redirect to is: https://appcenter.intuit.com/connect/oauth2 with parameters: client_id, redirect_uri, response_type=code, scope, and state (a random CSRF token you generate and verify). When the user approves, Intuit redirects to your redirect_uri with code and realmId parameters. Your callback function exchanges the code for tokens by POSTing to https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer with client_id:client_secret as Basic auth and the code in the body. The most critical step: after token exchange, store the access_token, refresh_token, AND the realmId in a Firestore document keyed to the user or company. When the access token expires, POST to the same token endpoint with grant_type=refresh_token and your current refresh_token. Intuit returns a new access_token AND a new refresh_token — write both back to Firestore immediately before using the new access token. If you fail to persist the new refresh_token, the next refresh will fail because the old one is already invalidated.

index.js
1// Firebase Cloud Function proxy — index.js (simplified)
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4const axios = require('axios');
5admin.initializeApp();
6
7const QB_TOKEN_URL = 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer';
8const QB_BASE_URL = 'https://sandbox-quickbooks.api.intuit.com'; // switch to production for live
9
10// Exchange refresh token for new tokens; always persist the new refresh_token
11async function refreshQBTokens(userId) {
12 const doc = await admin.firestore().doc(`qb_sessions/${userId}`).get();
13 const { refresh_token, realm_id } = doc.data();
14 const cfg = functions.config().quickbooks;
15 const basicAuth = Buffer.from(`${cfg.client_id}:${cfg.client_secret}`).toString('base64');
16 const resp = await axios.post(QB_TOKEN_URL,
17 `grant_type=refresh_token&refresh_token=${encodeURIComponent(refresh_token)}`,
18 { headers: { Authorization: `Basic ${basicAuth}`, 'Content-Type': 'application/x-www-form-urlencoded' } }
19 );
20 // CRITICAL: always write BOTH new tokens back before using them
21 await admin.firestore().doc(`qb_sessions/${userId}`).update({
22 access_token: resp.data.access_token,
23 refresh_token: resp.data.refresh_token, // new refresh token — replaces old one
24 updated: Date.now(),
25 });
26 return { access_token: resp.data.access_token, realm_id };
27}
28
29// Fetch invoices from QuickBooks — proxied through this function
30exports.qbGetInvoices = functions.https.onCall(async (data, context) => {
31 const userId = context.auth?.uid || 'default';
32 let session = (await admin.firestore().doc(`qb_sessions/${userId}`).get()).data();
33 let { access_token, realm_id } = session;
34 try {
35 const resp = await axios.get(
36 `${QB_BASE_URL}/v3/company/${realm_id}/query?query=SELECT * FROM Invoice MAXRESULTS 50&minorversion=65`,
37 { headers: { Authorization: `Bearer ${access_token}`, Accept: 'application/json' } }
38 );
39 return { invoices: resp.data.QueryResponse.Invoice || [] };
40 } catch (err) {
41 if (err.response?.status === 401) {
42 // Token expired — refresh and retry once
43 const refreshed = await refreshQBTokens(userId);
44 const retry = await axios.get(
45 `${QB_BASE_URL}/v3/company/${refreshed.realm_id}/query?query=SELECT * FROM Invoice MAXRESULTS 50&minorversion=65`,
46 { headers: { Authorization: `Bearer ${refreshed.access_token}`, Accept: 'application/json' } }
47 );
48 return { invoices: retry.data.QueryResponse.Invoice || [] };
49 }
50 throw err;
51 }
52});

Pro tip: Use Firebase Functions config (firebase functions:config:set quickbooks.client_id=... quickbooks.client_secret=...) to store your client credentials. Never hardcode them in the function file.

Expected result: The proxy functions are deployed. The OAuth callback function correctly stores access_token, refresh_token, and realm_id in Firestore. The qbGetInvoices function returns invoice data and handles 401 by refreshing tokens automatically.

3

Step 3: Capture the realmId and store it per connected company

The realmId is QuickBooks' identifier for a specific company account. It appears in the URL when a user is logged into QuickBooks Online (the number in the URL path, e.g., /app/yourcompany?id=1234567890), and Intuit also includes it as a realmId query parameter in the OAuth callback redirect URL when the user approves your app connection. In your OAuth callback function (Step 2), extract the realmId from the callback query parameters alongside the authorization code. Store it in the Firestore document for this user or company: { access_token, refresh_token, realm_id: realmId, connected_at: Date.now() }. Every QuickBooks API request in your proxy will embed this realmId in the URL path: /v3/company/{realmId}/invoice, /v3/company/{realmId}/query, and so on. Without the correct realmId, QuickBooks returns an authorization error that looks like a credential problem, not a missing-ID problem — which is why it is easy to miss. If your app supports multiple connected companies (e.g., an accountant managing several QuickBooks accounts), store each company's realmId separately keyed to a company ID or user ID. Pass a companyId parameter to your proxy functions so they fetch the correct realmId from Firestore for each request. For a single-company app (e.g., a business owner's own QuickBooks), you only ever store one realmId. In FlutterFlow, add a one-time screen or deep link handler that the user visits after completing the OAuth flow in a WebView or browser. This screen receives the completion signal from your proxy (e.g., a Firestore document update showing connected: true) and navigates to the main dashboard. Use a FlutterFlow Firebase Realtime Listener on the connection status document to detect when the OAuth flow completes in the background browser tab.

index.js
1// Extract realmId in the OAuth callback function
2exports.quickbooksCallback = functions.https.onRequest(async (req, res) => {
3 const { code, realmId, state } = req.query;
4 // TODO: verify state against stored CSRF token
5 const cfg = functions.config().quickbooks;
6 const basicAuth = Buffer.from(`${cfg.client_id}:${cfg.client_secret}`).toString('base64');
7 const tokenResp = await axios.post(QB_TOKEN_URL,
8 `grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(cfg.redirect_uri)}`,
9 { headers: { Authorization: `Basic ${basicAuth}`, 'Content-Type': 'application/x-www-form-urlencoded' } }
10 );
11 // Store tokens AND realmId together — both are required for every subsequent API call
12 await admin.firestore().doc(`qb_sessions/default`).set({
13 access_token: tokenResp.data.access_token,
14 refresh_token: tokenResp.data.refresh_token,
15 realm_id: realmId, // CRITICAL: store this from the callback, not from elsewhere
16 connected: true,
17 connected_at: Date.now(),
18 });
19 res.send('<h1>QuickBooks connected! You can close this window.</h1>');
20});

Pro tip: Never try to find the realmId from a QuickBooks API response — always capture it from the OAuth callback redirect URL. It is always present as a query parameter in the callback.

Expected result: After a user completes the OAuth flow, your Firestore document contains access_token, refresh_token, and realm_id for that company. Your proxy can now embed the realmId in API requests.

4

Step 4: Create a FlutterFlow API Group pointing to your proxy for invoice and financial data

With the proxy running and realmId stored, configure FlutterFlow to talk to your proxy. Click API Calls in the left navigation panel, then click + Add → Create API Group. Name it QuickBooksProxy. Set the Base URL to your Firebase Functions base URL: https://us-central1-YOUR-PROJECT-ID.cloudfunctions.net. Add a first API Call inside the group: name it GetInvoices. Set method to POST (Firebase callable functions use POST). Set the endpoint path to /qbGetInvoices. In the Body tab, add a JSON body: {} — the proxy handles everything else. In the Variables tab, you can add optional variables like maxResults (Integer, default 50) or statusFilter (String) that get passed to the proxy for more specific queries. Switch to Response & Test and paste a sample QuickBooks invoice response (see the code block below). Click Generate JSON Paths. FlutterFlow will create paths like $.invoices[0].DocNumber, $.invoices[0].TotalAmt, $.invoices[0].Balance, $.invoices[0].CustomerRef.name. Create a Data Type called QBInvoice with fields: docNumber (String), customerName (String), totalAmount (Decimal), balance (Decimal), status (String), dueDate (String). Map the JSON paths to this Data Type. Repeat this process to add a GetExpenses API Call and a GetBalances API Call as your app needs them. Each proxied call follows the same pattern: POST to the function name, parse the response, map to a Data Type.

typescript
1// Paste as sample in FlutterFlow Response & Test tab for GetInvoices
2{
3 "invoices": [
4 {
5 "Id": "1001",
6 "DocNumber": "INV-1001",
7 "TxnDate": "2025-01-10",
8 "DueDate": "2025-02-10",
9 "TotalAmt": 2500.00,
10 "Balance": 2500.00,
11 "CustomerRef": {
12 "value": "58",
13 "name": "Acme Corp"
14 },
15 "EmailStatus": "EmailSent"
16 }
17 ]
18}

Pro tip: QuickBooks uses a query language called QuickBooks Query Language (QQL) similar to SQL. Your proxy can accept a query string parameter and pass it to the QuickBooks /query endpoint — for example: SELECT * FROM Invoice WHERE Balance > '0' ORDER BY DueDate.

Expected result: The QuickBooksProxy API Group is configured in FlutterFlow with a GetInvoices call. Clicking Test API Call returns a 200 with invoice data from your Sandbox. JSON paths are generated and mapped to the QBInvoice Data Type.

5

Step 5: Build an invoices dashboard and bind Data Types to widgets

Add a new Screen called Invoices. Add a ListView widget. Set its Backend Query to the QuickBooksProxy/GetInvoices API Call. Set the list's data source to your QBInvoice Data Type. FlutterFlow automatically calls the proxy when this screen loads. Inside the ListView item template, add a Container with a card-style decoration (border radius 12, a light shadow, white background). Inside, add a Column containing: a Text widget showing the customer name (bound to QBInvoice.customerName), a Row with a Text widget for the invoice number (QBInvoice.docNumber) and a Text widget for the due date (QBInvoice.dueDate), and a Row showing the total amount and the outstanding balance side by side. Use a conditional color on the balance Text — red if QBInvoice.balance is greater than 0 (unpaid), green if zero (paid). Add a summary header above the ListView: a Row with three summary cards showing total outstanding, total paid this month, and number of overdue invoices. To compute these, use FlutterFlow's List functions: apply a filter on QBInvoice.balance > 0 for outstanding, and filter by dueDate < today AND balance > 0 for overdue. These computed counts let you show dashboard totals without additional API calls. Finally, add a pull-to-refresh gesture to the ListView by wrapping it in a RefreshIndicator widget (available in FlutterFlow's component library). Bind the refresh to the GetInvoices API Call — when the user pulls down, the list re-fetches from QuickBooks through the proxy.

Pro tip: For the amount Text widgets, apply number formatting in FlutterFlow's binding panel to display values as currency (e.g., $2,500.00). QuickBooks returns amounts as raw decimals without currency symbols.

Expected result: The Invoices screen displays a scrollable list of QuickBooks invoices with customer name, amount, balance, and due date. The header shows summary totals. Pull-to-refresh reloads data from the proxy.

6

Step 6: Flip the proxy from Sandbox to Production and handle token-rotation errors

When you are ready to connect to live QuickBooks company data, update your proxy environment to use the Production credentials and base URL. In your Firebase Functions config, update quickbooks.client_id and quickbooks.client_secret to the Production values from your Intuit developer app. Change the QB_BASE_URL constant in your proxy from sandbox-quickbooks.api.intuit.com to quickbooks.api.intuit.com. Redeploy the functions. Run the OAuth flow again with a real QuickBooks company — the Sandbox and Production OAuth endpoints are different environments, so a fresh token exchange is required. Once the Production realmId and tokens are stored in Firestore, your existing FlutterFlow API Calls to the proxy will automatically fetch live company data without any changes in FlutterFlow itself. For ongoing operations, monitor your Firestore qb_sessions documents for a healthy updated timestamp. If the timestamp stops advancing, the refresh-token rotation has failed — likely because a concurrent proxy call tried to refresh at the same time, and both wrote back the same (now invalidated) refresh token. To prevent this, use Firestore transactions in your refresh logic so only one concurrent call can refresh at a time. Also add error handling in FlutterFlow: if the proxy returns a 'token_invalid' error, show a screen prompting the user to reconnect their QuickBooks account — this triggers the OAuth flow again and issues fresh tokens. If you'd rather hand off the entire OAuth proxy setup and maintenance to specialists, RapidDev's team builds FlutterFlow + QuickBooks integrations and handles the token lifecycle on your behalf — free scoping call at rapidevelopers.com/contact.

Pro tip: Keep Sandbox and Production as separate Firebase Function configs rather than two separate functions. A single QB_ENV flag in your config switches between environments: sandbox-quickbooks.api.intuit.com vs quickbooks.api.intuit.com.

Expected result: The proxy connects to the Production QuickBooks environment with live company data. Token rotation errors are handled gracefully. If tokens become fully invalid, FlutterFlow navigates to a Reconnect screen.

Common use cases

Mobile invoices dashboard for small business owners

A business owner opens their FlutterFlow app and sees a list of their open, paid, and overdue invoices pulled live from QuickBooks Online. They can tap an invoice to see line items and the client total. The app polls their proxy for invoice data on load and when they pull to refresh — no manual export, no spreadsheet, just live QuickBooks data on their phone.

FlutterFlow Prompt

Show a list of QuickBooks invoices grouped by status (open, paid, overdue), with client name, amount, and due date. Tapping an invoice shows the line items.

Copy this prompt to try it in FlutterFlow

Expense tracking companion for field teams

A field service company builds a FlutterFlow app where managers can review recent expenses from QuickBooks on their phone while on-site with a client. The app displays expense categories, amounts, and vendors from QuickBooks Purchases, giving managers real-time visibility without logging into a desktop browser.

FlutterFlow Prompt

Display recent QuickBooks expenses from the past 30 days, grouped by expense category, with vendor name and amount. Allow filtering by date range.

Copy this prompt to try it in FlutterFlow

Cash-flow snapshot widget for a business dashboard app

A financial advisor builds a client-facing app that shows a live snapshot of a small business's QuickBooks balances — total accounts receivable, accounts payable, and bank balance — giving the client a single-screen cash-flow view. The balances are fetched from the QuickBooks API through the proxy and displayed as large summary cards at the top of a dashboard screen.

FlutterFlow Prompt

Show three summary cards: total open invoices (accounts receivable), total unpaid bills (accounts payable), and current bank balance, fetched from QuickBooks.

Copy this prompt to try it in FlutterFlow

Troubleshooting

401 Unauthorized error immediately after OAuth connection — appears to work then breaks

Cause: The proxy fetched a new access token using the refresh token but failed to persist the new refresh token. The next time it tries to refresh, the old refresh token is already invalidated and returns 401. Refresh-token rotation is the most common cause of silent QuickBooks integration failures.

Solution: In your token refresh code, immediately write both the new access_token AND the new refresh_token back to Firestore before using the new access token. Make the Firestore write and the token use part of the same atomic block. Add a Firestore transaction to prevent concurrent refresh race conditions.

typescript
1// Always update BOTH tokens together — never just the access token
2await admin.firestore().doc(`qb_sessions/${userId}`).update({
3 access_token: resp.data.access_token,
4 refresh_token: resp.data.refresh_token, // must be persisted immediately
5 updated: Date.now(),
6});

AuthorizationFailed or 401 error on every QuickBooks API request despite valid tokens

Cause: The realmId embedded in the API URL path is missing, wrong, or mismatched with the connected account. A Sandbox realmId used against the Production host, or a typo in the stored realmId, produces 401 errors that look identical to expired-token errors.

Solution: Verify that the realmId stored in your Firestore document exactly matches the realmId that appeared in the OAuth callback URL. Log the full API URL being called by your proxy (e.g., /v3/company/1234567890/invoice) to confirm the realmId is being interpolated correctly. Also verify that you are using the Sandbox base URL for Sandbox credentials and the Production base URL for Production credentials — mixing them returns auth errors.

XMLHttpRequest error when testing from FlutterFlow web preview

Cause: The Firebase Cloud Function does not include CORS headers that allow the FlutterFlow preview origin (https://app.flutterflow.io) to call it.

Solution: Use Firebase callable functions (onCall) instead of onRequest — callable functions include CORS handling automatically. If you must use onRequest, wrap your handler with the cors npm package configured with the FlutterFlow preview origin.

QuickBooks returns empty QueryResponse even though invoices exist in the account

Cause: The QQL query is missing conditions or pagination, or there is a Sandbox vs Production mismatch. The Sandbox environment has sample data but it may not match the queries you are testing.

Solution: Log the full QQL query string your proxy is sending. Test it directly in the QuickBooks API Explorer at developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/invoice#query-an-invoice. In Sandbox, use queries without date filters first to confirm any data is returned.

Best practices

  • Never put the QuickBooks client secret, access token, or refresh token in FlutterFlow API Call headers, App Values, or Dart code — keep everything in the proxy's server environment.
  • Always write the new refresh_token back to Firestore immediately when refreshing tokens — rotation means the old token is invalidated instantly and the new one must be persisted before anything else.
  • Store the realmId alongside the tokens in Firestore, not separately — they are always used together for every API request.
  • Build and test in the Sandbox environment with the Sandbox base URL before switching to Production; a single config variable flip should be all that is required.
  • Use Firestore transactions or Cloud Firestore document locking when refreshing tokens to prevent concurrent refresh race conditions that invalidate tokens.
  • Cache QuickBooks invoice and expense lists in Firestore for a few minutes rather than re-fetching on every screen navigation — this reduces API calls, improves perceived speed, and stays within Intuit's per-realm throttling limits.
  • Add a reconnect flow in FlutterFlow that triggers the OAuth flow again when the proxy returns a token_invalid signal — once a refresh token is fully expired or revoked, a fresh OAuth authorization is the only recovery path.
  • Start with read-only scopes (com.intuit.quickbooks.accounting) and add write permissions (POST invoices, update payments) only when your app needs them — minimal scopes limit damage if credentials are ever compromised.

Alternatives

Frequently asked questions

Can I put the QuickBooks client secret in a FlutterFlow App Value or environment variable?

No. FlutterFlow App Values and any constants you define in Dart are compiled into your app's binary. A decompiled APK or IPA exposes all hardcoded strings, including API secrets. The QuickBooks client secret must live exclusively in your Firebase Cloud Function or Supabase Edge Function config — never in anything that ships to users' devices.

Why does my QuickBooks integration stop working after 60-90 days?

This is almost always a refresh-token rotation failure. QuickBooks access tokens expire every 60 minutes and refresh tokens expire every 100 days. Each time your proxy refreshes the access token, QuickBooks issues a new refresh token and invalidates the old one. If your proxy does not immediately write the new refresh token back to Firestore after each refresh, the stored token becomes invalid and the next refresh fails. Fix: always persist both the new access_token and the new refresh_token together after every refresh call.

What is the realmId and where do I find it?

The realmId is QuickBooks' numeric identifier for a specific company account (e.g., 1234567890). It appears in the URL when a user is logged into QuickBooks Online. More importantly, Intuit includes it as a realmId query parameter in the OAuth callback redirect URL when a user approves your app. Your proxy's OAuth callback function should extract it from the redirect and store it in Firestore alongside the tokens. Every QuickBooks API request must include it in the URL path: /v3/company/{realmId}/invoice.

Do I need to handle the difference between Sandbox and Production?

Yes. Intuit's Sandbox and Production environments have different base URLs (sandbox-quickbooks.api.intuit.com vs quickbooks.api.intuit.com) and issue different client credentials. Develop and test against Sandbox — it includes sample company data without affecting real accounts. When ready for live data, update your proxy config with Production credentials and change the base URL. Do not mix Sandbox credentials with the Production host or vice versa; this always returns authentication errors.

What QuickBooks data can I read from a FlutterFlow app?

With the com.intuit.quickbooks.accounting scope, you can read invoices, bills, payments, estimates, customers, vendors, accounts, bank transactions, and financial reports (profit and loss, balance sheet). Anything accessible through the QuickBooks Online web interface is available via the API. Use QuickBooks Query Language (QQL) in your proxy to filter and sort — for example, SELECT * FROM Invoice WHERE Balance > '0.00' to get only unpaid invoices.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.