Connect Retool to QuickBooks Online by creating a REST API Resource with QuickBooks' OAuth 2.0 base URL, configuring token refresh using Retool's Custom Auth system, and building queries against the /v3/company/{realmId}/ endpoints. Once connected, you can build financial operations dashboards showing invoices, customers, expenses, and account balances — combining QuickBooks accounting data with your CRM or database in a single Retool panel.
| Fact | Value |
|---|---|
| Tool | QuickBooks |
| Category | Other |
| Method | REST API Resource |
| Difficulty | Intermediate |
| Time required | 35 minutes |
| Last updated | April 2026 |
Build a QuickBooks Financial Operations Dashboard in Retool
Finance and operations teams often need to work with QuickBooks data alongside other business data — comparing outstanding invoices against CRM opportunity values, reconciling expense reports against internal project codes, or generating cross-system financial summaries that QuickBooks alone cannot produce. QuickBooks' own reporting is comprehensive but isolated — it does not know about your CRM, project management tool, or internal databases. Retool bridges this gap by pulling QuickBooks data into a unified internal tool.
With a Retool–QuickBooks integration, finance teams can view open invoices filtered by customer, due date, and amount — with one-click access to mark payments received. Operations teams can track vendor bills and expenses against internal project budgets. Executives can see combined financial dashboards showing QuickBooks P&L figures alongside CRM pipeline data, giving a complete picture of revenue from quote to cash.
QuickBooks Online's REST API uses OAuth 2.0 with short-lived access tokens (1-hour expiry) and long-lived refresh tokens (100-day expiry). Retool's Custom Auth system handles this token rotation automatically — once configured, the resource refreshes tokens transparently without interrupting user sessions. The company-specific realm ID (a numeric identifier unique to each QuickBooks company) is embedded in every API endpoint path, so correctly capturing and configuring this value is an essential setup step.
Integration method
QuickBooks Online connects to Retool via a REST API Resource using OAuth 2.0 with Custom Auth for automatic token refresh. Queries target QuickBooks' REST API v3 endpoints under /v3/company/{realmId}/ to retrieve invoices, customers, vendors, accounts, and transactions. Retool proxies all requests server-side, eliminating CORS issues and keeping OAuth tokens off the browser. Refresh token rotation requires a one-time Intuit app setup in the Intuit Developer Portal.
Prerequisites
- A QuickBooks Online company account (any paid plan — QuickBooks Online Sandbox accounts work for development)
- An Intuit Developer account (developer.intuit.com) with a QuickBooks Online app created and OAuth 2.0 credentials (Client ID and Client Secret) configured
- The QuickBooks company realm ID: a numeric identifier found in the URL when logged into QuickBooks Online (e.g., the number after /app/ in https://qbo.intuit.com/app/companyinfo/REALMID)
- Authorization through the QuickBooks OAuth 2.0 flow to obtain an initial access token and refresh token for your company (can be done via Postman or the Intuit Developer sandbox playground)
- A Retool account with permission to create and edit Resources
Step-by-step guide
Create an Intuit Developer app and configure OAuth 2.0
Before configuring Retool, you need an Intuit Developer app to obtain OAuth 2.0 credentials. Go to developer.intuit.com and sign in with your Intuit account. Navigate to 'My Apps' and click 'Create an app'. Select 'QuickBooks Online and Payments' as the platform. Give the app a name (e.g., 'Retool Integration') and click Create. In the app settings: 1. Go to the 'Development' section → 'Keys & OAuth'. Copy the 'Client ID' and 'Client Secret' — you will need these in Retool. 2. Scroll to 'Redirect URIs' and add Retool's OAuth callback URL. For Retool Cloud, this is `https://oauth.retool.com/oauth/oauthcallback`. For self-hosted Retool, it is `https://your-retool-domain.com/oauth/oauthcallback`. 3. Under 'Scopes', ensure 'com.intuit.quickbooks.accounting' is selected (required for all accounting data access). For production (live QuickBooks company data), you will need to go through Intuit's app listing process or use a production app. For development and testing, the sandbox app with sandbox credentials works against QuickBooks' test company data. To obtain your initial refresh token, use the Intuit OAuth 2.0 Playground (developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0-playground) or the Postman collection Intuit provides. Complete the OAuth authorization flow, then copy the resulting access_token, refresh_token, and x_refresh_token_expires_in values. Capture your QuickBooks company's realm ID: log into QuickBooks Online at qbo.intuit.com and look at the URL — the numeric value after '/app/' in the URL is your realm ID (e.g., 1234567890123456789).
1// QuickBooks OAuth 2.0 token endpoint and scopes reference2// Token URL (for refresh token exchange):3// https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer4//5// Authorization URL:6// https://appcenter.intuit.com/connect/oauth27//8// Required scope for accounting data:9// com.intuit.quickbooks.accounting10//11// Base API URL:12// https://quickbooks.api.intuit.com13//14// Sandbox Base API URL:15// https://sandbox-quickbooks.api.intuit.comPro tip: Keep the sandbox app (for development) and production app (for live company data) as separate Intuit Developer apps with separate Client IDs and Client Secrets. Create two Retool Resources — 'QuickBooks Sandbox' and 'QuickBooks Production' — to prevent accidental writes to live financial data during development.
Expected result: You have an Intuit Developer app with Client ID, Client Secret, the Retool redirect URI configured, and an initial access token + refresh token obtained through the OAuth flow.
Configure the QuickBooks REST API Resource in Retool with Custom Auth
Navigate to the Resources tab in Retool and click Add Resource. Select 'REST API' as the resource type. In the resource configuration form: - 'Resource name': Enter 'QuickBooks Online' or 'QuickBooks Production'. - 'Base URL': Enter `https://quickbooks.api.intuit.com` (or `https://sandbox-quickbooks.api.intuit.com` for sandbox). - 'Headers': Add `Accept: application/json` and `Content-Type: application/json` as default headers. For authentication, scroll to the 'Authentication' section and select 'Custom Auth' from the dropdown. Custom Auth allows you to configure a token refresh flow that Retool runs automatically when an API call returns a 401. In the Custom Auth 'Login' section, configure the initial token exchange: - Step 1 (HTTP Request): POST to `https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer` with body `grant_type=refresh_token&refresh_token=YOUR_INITIAL_REFRESH_TOKEN` and header `Authorization: Basic BASE64(CLIENT_ID:CLIENT_SECRET)`. - Step 2 (Set Variable): Set `access_token = {{ step1.response.access_token }}` and `refresh_token = {{ step1.response.refresh_token }}`. In the Custom Auth 'Headers' section, add: `Authorization: Bearer {{ auth.access_token }}`. In the 'Refresh Token' section (triggered on 401 responses), configure the same token refresh steps as the Login section above — this is what enables automatic token rotation when the 1-hour access token expires. Store the Client ID and Client Secret as Retool Configuration Variables (Settings → Configuration Variables) marked as secret, and reference them as `{{ environment.variables.QB_CLIENT_ID }}` in the auth configuration. Click Save Changes.
1// Base64 encode CLIENT_ID:CLIENT_SECRET for Basic auth header2// In Custom Auth HTTP request step, set Authorization header to:3// Basic {{ btoa(environment.variables.QB_CLIENT_ID + ':' + environment.variables.QB_CLIENT_SECRET) }}4//5// Token request body (form-encoded):6// grant_type=refresh_token&refresh_token={{ auth.refresh_token || environment.variables.QB_INITIAL_REFRESH_TOKEN }}Pro tip: QuickBooks refresh tokens expire after 100 days if not used. If your Retool integration is inactive for more than 100 days, you will need to re-authorize through the OAuth flow to obtain a new refresh token. Set a calendar reminder at the 90-day mark to trigger a manual refresh, or build a Retool Workflow that calls the token endpoint monthly to keep the refresh token active.
Expected result: The QuickBooks REST API Resource is configured with Custom Auth. Sending a test request to `/v3/companyinfo/REALM_ID?minorversion=65` returns a 200 response with company information, confirming that the OAuth tokens are working.
Query QuickBooks invoices, customers, and accounts
QuickBooks Online's REST API v3 uses two query patterns: direct entity endpoints for individual records, and a SQL-like Query endpoint (IDS Query Language) for filtering and listing entities. Create your first query: click + New query in the Code panel, select the QuickBooks Resource, set Method to GET, and set the Path to: `/v3/company/{{ environment.variables.QB_REALM_ID }}/query?query=SELECT * FROM Invoice WHERE Balance > 0 ORDER BY DueDate ASC MAXRESULTS 100&minorversion=65` Store the realm ID as a Configuration Variable 'QB_REALM_ID' so it can be referenced consistently across all QuickBooks queries. Name this query 'getOpenInvoices'. The response returns an array of Invoice objects under `response.QueryResponse.Invoice`. Common QuickBooks IDS Query examples: - Open invoices: `SELECT * FROM Invoice WHERE Balance > 0 ORDER BY DueDate ASC MAXRESULTS 100` - Customers: `SELECT * FROM Customer WHERE Active = true MAXRESULTS 100` - Vendor bills unpaid: `SELECT * FROM Bill WHERE Balance > 0 ORDER BY DueDate ASC MAXRESULTS 100` - Chart of accounts: `SELECT * FROM Account WHERE AccountType = 'Accounts Receivable' MAXRESULTS 100` - Payments received: `SELECT * FROM Payment WHERE TxnDate >= '{{ dateFilter.startDate }}' MAXRESULTS 100` Add a JavaScript transformer to the getOpenInvoices query to flatten the nested response structure for display in a Table.
1// Transformer: flatten QuickBooks Invoice response for Table display2// Raw response path: data.QueryResponse.Invoice3const invoices = data?.QueryResponse?.Invoice || [];45return invoices.map(inv => ({6 id: inv.Id,7 doc_number: inv.DocNumber || 'N/A',8 customer: inv.CustomerRef?.name || 'Unknown',9 customer_id: inv.CustomerRef?.value,10 txn_date: inv.TxnDate,11 due_date: inv.DueDate,12 total: parseFloat(inv.TotalAmt || 0).toFixed(2),13 balance: parseFloat(inv.Balance || 0).toFixed(2),14 days_overdue: inv.DueDate15 ? Math.max(0, Math.floor((Date.now() - new Date(inv.DueDate)) / 86400000))16 : 0,17 status: parseFloat(inv.Balance) <= 0 ? 'Paid' : new Date(inv.DueDate) < new Date() ? 'Overdue' : 'Open',18 currency: inv.CurrencyRef?.value || 'USD',19 link: inv.InvoiceLink || ''20}));Pro tip: QuickBooks IDS Query Language uses MAXRESULTS (not LIMIT) with a maximum of 1000 per query. For pagination, use STARTPOSITION: `SELECT * FROM Invoice MAXRESULTS 100 STARTPOSITION {{ (currentPage.value - 1) * 100 + 1 }}`. Note that STARTPOSITION is 1-based, not 0-based — STARTPOSITION 1 returns the first record.
Expected result: The getOpenInvoices query returns open invoices from QuickBooks. The transformer flattens the response into a clean array with customer name, amounts, and computed days_overdue. The data is ready to bind to a Retool Table component.
Build the financial operations dashboard UI
With queries returning data, build the dashboard UI in Retool's canvas view. Drag a Table component onto the canvas and name it 'invoiceTable'. Set its Data to `{{ getOpenInvoices.data }}`. Configure the following columns: - doc_number: rename to 'Invoice #' - customer: rename to 'Customer' - txn_date: rename to 'Invoice Date' - due_date: rename to 'Due Date' - total: rename to 'Total', format as currency - balance: rename to 'Balance', format as currency - days_overdue: rename to 'Days Overdue' - status: rename to 'Status', add tag coloring: green for 'Paid', orange for 'Open', red for 'Overdue' Add filter controls above the table: - A Text Input 'customerFilter' for customer name search - A Date Range Picker 'dateFilter' for transaction date filtering - A Select component 'statusFilter' with options: All, Open, Overdue, Paid Update the getOpenInvoices query to use these filters in the IDS query: ``` SELECT * FROM Invoice WHERE CustomerRef.name LIKE '%{{ customerFilter.value }}%' AND Balance > 0 MAXRESULTS 100 ``` Add a summary stats bar at the top with Text components showing: - Total open balance: `${{ getOpenInvoices.data.reduce((sum, inv) => sum + parseFloat(inv.balance), 0).toFixed(2) }}` - Number of overdue invoices: `{{ getOpenInvoices.data.filter(inv => inv.status === 'Overdue').length }}` - Oldest overdue: `{{ Math.max(...getOpenInvoices.data.map(inv => inv.days_overdue)) }} days` Add a detail panel on the right side: a Container that appears when an invoice row is selected, showing line item details from a second query 'getInvoiceDetail' bound to `{{ invoiceTable.selectedRow.id }}`.
1// Query: get single invoice detail with line items2// Path: /v3/company/{{ environment.variables.QB_REALM_ID }}/invoice/{{ invoiceTable.selectedRow.id }}?minorversion=653// Method: GET45// Transformer for line items:6const invoice = data?.Invoice;7if (!invoice) return [];89const lines = invoice.Line || [];10return lines11 .filter(line => line.DetailType === 'SalesItemLineDetail')12 .map(line => ({13 description: line.Description || line.SalesItemLineDetail?.ItemRef?.name || 'N/A',14 quantity: line.SalesItemLineDetail?.Qty || 1,15 unit_price: parseFloat(line.SalesItemLineDetail?.UnitPrice || 0).toFixed(2),16 amount: parseFloat(line.Amount || 0).toFixed(2)17 }));Pro tip: Use Retool's Table 'Row color' conditional formatting to highlight overdue invoices: set condition `{{ row.status === 'Overdue' }}` with a light red background color. This gives finance staff an immediate visual signal for urgent follow-up without needing to filter explicitly.
Expected result: The dashboard shows a complete accounts receivable view: summary stats at the top, a filterable invoice table with status color-coding, and a detail panel showing line items for the selected invoice.
Write back to QuickBooks and combine with CRM data
Beyond read operations, Retool can write to QuickBooks via POST and PUT requests. Common write operations include creating new invoices, updating payment status, and voiding transactions. Create a 'Record Payment' query: Method POST, Path `/v3/company/{{ environment.variables.QB_REALM_ID }}/payment?minorversion=65`, Body (JSON): ```json { "TotalAmt": {{ parseFloat(paymentAmountInput.value) }}, "CustomerRef": { "value": "{{ invoiceTable.selectedRow.customer_id }}" }, "Line": [{ "Amount": {{ parseFloat(paymentAmountInput.value) }}, "LinkedTxn": [{ "TxnId": "{{ invoiceTable.selectedRow.id }}", "TxnType": "Invoice" }] }] } ``` Add a payment form below the invoice table: amount input, payment date picker, payment method Select, and a 'Record Payment' button wired to this query with a confirmation modal. For cross-system data joining, combine QuickBooks customer data with your CRM: create a second query ('getCRMCustomers') against your PostgreSQL or Salesforce resource. In a JavaScript transformer, join the datasets on email address: ```javascript const qbCustomers = getQBCustomers.data?.QueryResponse?.Customer || []; const crmCustomers = getCRMCustomers.data || []; const crmByEmail = {}; crmCustomers.forEach(c => { crmByEmail[c.email?.toLowerCase()] = c; }); return qbCustomers.map(qb => ({ ...qb, crm_account: crmByEmail[qb.PrimaryEmailAddr?.Address?.toLowerCase()] || null, in_crm: !!crmByEmail[qb.PrimaryEmailAddr?.Address?.toLowerCase()] })); ``` For complex QuickBooks integrations involving journal entries, multi-currency transactions, custom field management, and advanced reporting across multiple QuickBooks companies, RapidDev's team can help design and build the full Retool solution.
1// JavaScript transformer: join QuickBooks customers with CRM data2// Assumes getQBCustomers and getCRMCustomers queries have run3const qbCustomers = getQBCustomers.data?.QueryResponse?.Customer || [];4const crmData = getCRMCustomers.data || [];56// Build lookup map from CRM by email7const crmByEmail = {};8crmData.forEach(c => {9 if (c.email) crmByEmail[c.email.toLowerCase()] = c;10});1112return qbCustomers.map(qb => {13 const email = qb.PrimaryEmailAddr?.Address?.toLowerCase() || '';14 const crm = crmByEmail[email] || null;15 return {16 qb_id: qb.Id,17 name: qb.DisplayName || qb.FullyQualifiedName,18 email: email,19 phone: qb.PrimaryPhone?.FreeFormNumber || '',20 balance: parseFloat(qb.Balance || 0).toFixed(2),21 // CRM-enriched fields22 in_crm: !!crm,23 crm_owner: crm?.owner_name || 'Not in CRM',24 crm_stage: crm?.pipeline_stage || 'N/A',25 crm_arr: crm?.annual_revenue ? `$${parseFloat(crm.annual_revenue).toLocaleString()}` : 'N/A'26 };27});Pro tip: QuickBooks Online's API has rate limits: 500 requests per minute for most endpoints, and 10 requests per second. If your Retool app queries multiple QuickBooks endpoints simultaneously on page load, add a small delay between queries using Retool's 'Run after' query dependency setting. This avoids 429 errors during initial dashboard load.
Expected result: The payment recording form submits to QuickBooks and the invoice table refreshes showing the updated balance. The cross-system reconciliation panel shows QuickBooks customers enriched with CRM data, highlighting customers not present in either system.
Common use cases
Build an accounts receivable dashboard for the finance team
Create a Retool app that queries QuickBooks for all open invoices, displays them in a Table with customer name, invoice number, amount, due date, and days overdue, and highlights overdue invoices in red. Finance staff can filter by customer, date range, and amount, click to view invoice line items in a detail panel, and trigger payment reminders through a connected email resource. The dashboard reduces time spent navigating QuickBooks' invoice reports.
Build a Retool accounts receivable dashboard with a Table of open QuickBooks invoices showing CustomerRef.name, DocNumber, TotalAmt, DueDate, and Balance, color-coded rows for overdue invoices (DueDate < today), filter inputs for customer and date range, and a detail panel showing line items when a row is selected.
Copy this prompt to try it in Retool
Build a cross-system financial reconciliation panel
Create a Retool app that joins QuickBooks customer invoice data with your CRM's opportunity records to identify closed-won deals without corresponding invoices, or invoices for customers not in the CRM. Finance operations teams use this to catch billing gaps and reconcile the two systems. A JavaScript transformer joins the datasets in-memory using customer email as the join key.
Build a Retool reconciliation panel that queries QuickBooks customers and invoices alongside Salesforce closed-won opportunities, uses a transformer to join on customer email, and displays a Table highlighting mismatches: closed-won deals with no invoice, and invoices with no matching opportunity.
Copy this prompt to try it in Retool
Build a vendor expense tracking and approval dashboard
Create a Retool app that queries QuickBooks for unpaid vendor bills, displays them grouped by vendor and category, and lets operations managers approve or reject expenses. Approved bills trigger a QuickBooks API update to mark the bill as approved, and rejected bills log a rejection reason. The panel replaces email-based approval workflows with a structured, auditable process.
Build a Retool vendor bill dashboard querying QuickBooks Bills with UNPAID status, showing VendorRef.name, TxnDate, DueDate, TotalAmt, and category, with Approve and Reject buttons that update the bill status via QuickBooks API and log the decision to a PostgreSQL audit table.
Copy this prompt to try it in Retool
Troubleshooting
QuickBooks API returns 401 Unauthorized even after configuring Custom Auth in Retool
Cause: The OAuth access token has expired (1-hour lifetime), the refresh token has been invalidated (100-day lifetime or revoked via Intuit Developer Console), or the Custom Auth refresh step is incorrectly configured and not exchanging the refresh token correctly.
Solution: First, verify the Custom Auth configuration: go to the Resources tab, open your QuickBooks resource, and test the refresh token step manually. Confirm the token endpoint URL is `https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer`, the body is form-encoded (not JSON) with `grant_type=refresh_token`, and the Authorization header uses Base64-encoded `client_id:client_secret`. If the refresh token itself is expired (over 100 days old), you must re-authorize through the Intuit OAuth playground to obtain a new refresh token and update the Custom Auth configuration.
QuickBooks query returns 'AuthorizationFault: message=StatusCode: 401, ReasonPhrase: Unauthorized'
Cause: The realm ID in the API path is incorrect, or the authenticated user does not have access to the specified QuickBooks company. QuickBooks returns 401 (not 403) for both token and realm ID authorization failures.
Solution: Verify the realm ID by logging into QuickBooks Online and checking the URL — the realm ID is the long numeric string in the URL path. For example, in `https://qbo.intuit.com/app/dashboard?realm=1234567890123456789`, the realm ID is `1234567890123456789`. Confirm you are using the correct environment (sandbox vs production) — sandbox tokens do not work against production endpoints and vice versa.
QuickBooks IDS query returns empty results even though data exists in the QuickBooks app
Cause: The IDS query syntax is incorrect, the entity name is wrong (IDS entity names are case-sensitive and singular: 'Invoice' not 'Invoices'), or the query is missing a required MAXRESULTS clause. QuickBooks also silently returns empty results if the query filter references a field name that does not exist.
Solution: Verify the IDS query syntax using QuickBooks' API Explorer (developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities). Entity names are singular and PascalCase (Invoice, Customer, Bill, Payment, Account). All IDS queries require a MAXRESULTS clause (maximum 1000). Test the query directly in the QuickBooks API Explorer before using it in Retool to confirm it returns data in isolation.
1// Correct IDS query syntax examples2// Wrong: SELECT * FROM Invoices LIMIT 1003// Right: SELECT * FROM Invoice MAXRESULTS 1004//5// Wrong: SELECT * FROM invoice WHERE balance > 06// Right: SELECT * FROM Invoice WHERE Balance > '0' MAXRESULTS 1007//8// Note: compare numeric fields as strings in IDS queriesQuickBooks API returns 429 Too Many Requests errors on page load
Cause: Retool loads multiple QuickBooks queries simultaneously on page open. QuickBooks' rate limit is 500 requests per minute total, but if multiple users have the Retool app open simultaneously, the combined request rate can exceed this threshold quickly.
Solution: Add a 'Run after' dependency to secondary QuickBooks queries so they fire sequentially rather than in parallel. In the query editor, set 'Run after query' to the primary query name. Alternatively, add query caching to read-only queries (e.g., getCustomers, getAccounts) with a 60-second cache duration — this significantly reduces API calls when multiple users have the dashboard open. For high-traffic Retool apps with many QuickBooks queries, consider using a Retool Workflow to pre-fetch and cache frequently accessed QuickBooks data.
Best practices
- Store the QuickBooks Client ID, Client Secret, realm ID, and initial refresh token in Retool Configuration Variables (Settings → Configuration Variables) marked as secret. Never embed these values directly in query paths or body fields — they would be visible to any Retool user who views the query.
- Use separate Intuit Developer apps (with separate Client IDs and Client Secrets) for sandbox and production environments. Create two Retool Resources — 'QuickBooks Sandbox' and 'QuickBooks Production' — and restrict access to the Production resource to finance team members only using Retool's resource permissions.
- Add MAXRESULTS to every QuickBooks IDS query to explicitly control result size. QuickBooks has a default return limit that varies by entity, and omitting MAXRESULTS can lead to inconsistent result counts. Use STARTPOSITION for pagination when you need more than 1000 records.
- Always transform the deeply nested QuickBooks API response in a query transformer before binding it to Retool components. QuickBooks responses use nested reference objects (e.g., `CustomerRef.name` and `CustomerRef.value`) — flatten these in a transformer so component bindings are simple property names rather than nested expressions.
- Add confirmation modals to all QuickBooks write operations (creating invoices, recording payments, voiding transactions). Financial data errors are difficult to reverse in QuickBooks — a payment recorded to the wrong invoice requires manual journal entry corrections. Require operators to review the details before confirming any write.
- Cache read-only QuickBooks queries (customers list, chart of accounts, vendor list) in Retool with a 5-10 minute cache duration. These lists change infrequently and caching them reduces API call volume, avoids rate limits, and speeds up the dashboard significantly.
- Monitor QuickBooks API rate limit usage by checking the `X-RateLimit-Remaining-Minute` response header returned by QuickBooks. If this value approaches zero regularly, implement request queuing in your Retool Workflow or increase cache durations on high-frequency queries.
- Test all QuickBooks write operations (invoice creation, payment recording) against the sandbox environment before enabling them in production. QuickBooks sandbox companies are completely isolated from production data and support the same API operations, making them safe for end-to-end write testing.
Alternatives
Use Xero if your organization operates primarily in the UK, Australia, or New Zealand market — Xero uses a similar OAuth 2.0 REST API but with tenant IDs instead of realm IDs, and is more widely used outside North America.
Choose FreshBooks if your business uses FreshBooks for freelance or agency invoicing — FreshBooks connects via REST API Resource in Retool with API key authentication, which is simpler to configure than QuickBooks' OAuth 2.0 flow.
Use Zoho Books if your team is already on the Zoho suite (CRM, Desk, Projects) and wants native cross-product integration — Zoho Books connects via REST API Resource with OAuth 2.0 and integrates with other Zoho platform tools.
Frequently asked questions
Does Retool support QuickBooks Desktop (not Online)?
No. Retool connects to QuickBooks Online via its REST API. QuickBooks Desktop uses a completely different integration model based on the Intuit Web Connector (a Windows-based sync tool) or the QuickBooks Desktop SDK — neither of which is accessible via REST API. If you are on QuickBooks Desktop and need to build Retool tools, consider migrating to QuickBooks Online, or use an ETL tool to sync Desktop data to a PostgreSQL database that Retool can query directly.
What is a QuickBooks realm ID and where do I find it?
The realm ID is a numeric identifier unique to each QuickBooks Online company. It appears in the URL when you are logged into QuickBooks Online — for example, in `https://qbo.intuit.com/app/dashboard?realm=1234567890123456789`, the realm ID is `1234567890123456789`. The realm ID is also returned in the OAuth authorization response as `realmId`. It is required in every QuickBooks API endpoint path: `/v3/company/{realmId}/query`.
How often do QuickBooks OAuth tokens need to be refreshed in Retool?
QuickBooks access tokens expire after 1 hour. Retool's Custom Auth automatically refreshes them when a 401 response is received, so end users should never see token expiry errors in practice. The refresh token has a 100-day expiry — if the refresh token expires, a Retool admin must re-authorize through the Intuit OAuth flow and update the Custom Auth configuration with the new refresh token.
Can Retool create and send invoices in QuickBooks?
Yes. QuickBooks' REST API supports creating Invoice entities via POST to `/v3/company/{realmId}/invoice`. Retool can send this request from a form-based workflow where an operator fills in customer, line items, due date, and amount. To email the invoice to the customer directly from QuickBooks (using QuickBooks' email delivery), make a second API call to `/v3/company/{realmId}/invoice/{id}/send` with the recipient email address in the request body.
How does QuickBooks' IDS Query Language differ from standard SQL?
QuickBooks IDS (Intuit Developer Services) Query Language resembles SQL but with several differences: it uses MAXRESULTS instead of LIMIT, STARTPOSITION instead of OFFSET (and is 1-based), entity names are singular PascalCase (Invoice not invoices), it does not support JOINs (all related data comes nested in the response), string comparisons use single quotes, and the LIKE operator uses % wildcards. It also does not support aggregation functions (SUM, COUNT) — those calculations must be done in a Retool JavaScript transformer after fetching the raw data.
Can I connect multiple QuickBooks companies to the same Retool instance?
Yes. Each QuickBooks company has a different realm ID and requires its own OAuth authorization. Create one Retool REST API Resource per company, each with its own Custom Auth configuration (separate Client ID, Client Secret, and refresh token per company). In your Retool app, use a Select component to switch between company resources, and dynamically reference the selected resource using Retool's resource selection pattern.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation