Loopfour
IntegrationsAccounting

QuickBooks Online

Accounting integration for invoices, payments, and financial data

QuickBooks Online

Connect to QuickBooks Online to automate invoice creation, payment processing, customer management, and financial reporting.

Overview

The existing Accounting connection exposes 198 searchable actions in the canvas and workflow builder. Chat receives the same action names, parameters, and outputs. The catalog covers the Intuit Accounting API reference, including 30 report endpoints. Availability depends on the connected company's region, subscription, and enabled features. QuickBooks Payments uses a separate API and authorization scope.

AreaAvailable operations
Accounting recordsCustomers, vendors, accounts, items, invoices, payments, bills, bill payments, journal entries, purchases, purchase orders, credits, deposits, transfers, estimates and sales/refund receipts
Company and setupCompany info, preferences, currencies, exchange rates, classes, departments, employees, terms, payment methods, taxes and entitlements
Advanced recordsBudgets, change orders, inventory adjustments, recurring transactions, time activities and reimbursement charges where Intuit supports them
Documents and bulk workPDF export, attachment upload/download, note attachments, batch requests, change data capture and queries
ReportingAll 30 documented report endpoint names, with complete headers, columns and nested rows available to later workflow steps

Configure an action

Choose QuickBooks, select the connected account, search Action, and set Company (Realm) ID. The form shows fields for that operation. Existing action IDs and input fields remain compatible with saved workflows.

New read actions use recordId. New create, update and delete actions use a Data JSON field with Intuit's field names. For example, Create Estimate accepts:

{
  "CustomerRef": { "value": "123" },
  "Line": [{
    "Amount": 100,
    "DetailType": "SalesItemLineDetail",
    "SalesItemLineDetail": { "ItemRef": { "value": "1" }, "Qty": 1, "UnitPrice": 100 }
  }]
}

Use IDs from the connected company. Updates and deletes require the record's current Id and SyncToken. Full updates replace omitted fields; use sparse: true only for actions whose Intuit reference supports sparse updates. The existing customer, invoice, bill, payment and journal-entry actions retain their individual fields and automatic token reads where already supported.

Create Item takes the item in Data. Name is required. A NonInventory or Service item also needs IncomeAccountRef; an Inventory item needs Intuit's inventory fields as well (TrackQtyOnHand, QtyOnHand, InvStartDate, AssetAccountRef, ExpenseAccountRef). QuickBooks requires the name to be unique across items, so query by name before creating one. For example:

{
  "Name": "Gateway 2.0",
  "Type": "NonInventory",
  "IncomeAccountRef": { "value": "358" },
  "Sku": "GW-2",
  "Description": "Gateway 2.0",
  "UnitPrice": 399.99,
  "Taxable": true
}

Batch accepts 1–30 batchItems with unique bId values. Inspect every BatchItemResponse for Fault, even when the HTTP request succeeds. Change Data Capture accepts entities and a changedSince ISO timestamp within the previous 30 days.

PDF actions return contentBase64, contentType, and size. Upload Attachment accepts a filename, MIME type, base64 content (up to 20 MB), and optional QBO attachment metadata. Download Attachment returns a temporary downloadUrl. Get Entitlements preserves the XML response as entitlementsXml.

Prerequisites

  • QuickBooks Online account (Plus, Essentials, or Simple Start)
  • Admin access to configure OAuth connection
  • Company file (Realm ID) for API access

Authentication

QuickBooks uses OAuth 2.0 for authentication via Nango.

Connect QuickBooks

# Get OAuth URL
curl "https://workflow.loopfour.ai/api/v1/connections/quickbooks/auth-url" \
  -H "x-api-key: YOUR_API_KEY"

Required Scopes

ScopeDescription
com.intuit.quickbooks.accountingFull accounting access

Available Actions

Customer Actions

createCustomer

Create a new customer in QuickBooks.

{
  "id": "create-customer",
  "type": "action",
  "action": "quickbooks.createCustomer",
  "config": {
    "displayName": "{{input.companyName}}",
    "firstName": "{{input.firstName}}",
    "lastName": "{{input.lastName}}",
    "companyName": "{{input.companyName}}",
    "email": "{{input.email}}",
    "phone": "{{input.phone}}",
    "billingAddress": {
      "line1": "{{input.address.street}}",
      "city": "{{input.address.city}}",
      "state": "{{input.address.state}}",
      "postalCode": "{{input.address.zip}}",
      "country": "{{input.address.country}}"
    }
  }
}

Parameters:

FieldTypeRequiredDescription
displayNamestringYesDisplay name (unique identifier)
firstNamestringNoFirst name
lastNamestringNoLast name
companyNamestringNoCompany name
emailstringNoPrimary email address
phonestringNoPhone number
mobilestringNoMobile number
billingAddressobjectNoBilling address
notesstringNoInternal notes
activebooleanNoActive status (default: true)

getCustomer

Get a customer by ID.

{
  "action": "quickbooks.getCustomer",
  "config": {
    "customerId": "{{input.customerId}}"
  }
}

updateCustomer

Update an existing customer.

{
  "action": "quickbooks.updateCustomer",
  "config": {
    "customerId": "{{steps.lookup.output.id}}",
    "email": "{{input.newEmail}}"
  }
}

findCustomerByEmail

Find a customer by email address.

{
  "action": "quickbooks.findCustomerByEmail",
  "config": {
    "email": "{{input.email}}"
  }
}

listCustomers

List customers with optional filters.

{
  "action": "quickbooks.listCustomers",
  "config": {
    "active": true,
    "companyName": "Acme",
    "limit": 50,
    "offset": 0
  }
}

Invoice Actions

createInvoice

Create a new invoice.

{
  "id": "create-invoice",
  "type": "action",
  "action": "quickbooks.createInvoice",
  "config": {
    "customerId": "{{steps.customer.output.Customer.Id}}",
    "lines": [
      {
        "description": "Professional Services",
        "amount": 1500.00,
        "quantity": 10,
        "unitPrice": 150.00
      },
      {
        "description": "Software License",
        "amount": 500.00,
        "quantity": 1,
        "unitPrice": 500.00,
        "itemId": "123"
      }
    ],
    "dueDate": "2024-02-15",
    "invoiceDate": "2024-01-15",
    "invoiceNumber": "INV-001",
    "customerMemo": "Thank you for your business!",
    "email": "{{input.customerEmail}}"
  }
}

Parameters:

FieldTypeRequiredDescription
customerIdstringYesCustomer ID
linesarrayYesLine items (at least one)
lines[].descriptionstringNoLine item description
lines[].amountnumberYesLine amount
lines[].quantitynumberNoQuantity (default: 1)
lines[].unitPricenumberNoUnit price
lines[].itemIdstringNoProduct/Service ID
lines[].taxCodeIdstringNoTax code (TAX, NON, or a tax-code id) → SalesItemLineDetail.TaxCodeRef; requires txnTaxDetail
dueDatestringNoDue date (YYYY-MM-DD)
invoiceDatestringNoInvoice date (default: today)
invoiceNumberstringNoCustom invoice number
customerMemostringNoMemo for customer
privateNotestringNoInternal note
emailstringNoEmail to send invoice to
txnTaxDetailobjectNo{ totalTax, txnTaxCodeId } — both required → TxnTaxDetail (see below)
shipAddrobjectNo{ line1, line2?, city?, countrySubDivisionCode?, postalCode?, country? } → ShipAddr
billAddrobjectNoSame shape → BillAddr

Externally computed sales tax. A company on US Automated Sales Tax lets QuickBooks compute tax from the ship-to address. To carry a tax computed elsewhere (for example by Avalara) instead, send the override as one unit: txnTaxDetail.totalTax, txnTaxDetail.txnTaxCodeId, and a taxCodeId on every line. Intuit only honours the override when TxnTaxDetail.TotalTax and TxnTaxDetail.TxnTaxCodeRef are both present — a totalTax-only body returns 200 and QuickBooks silently recomputes the tax — so each of these is rejected before the request is sent:

  • txnTaxDetail without txnTaxCodeId;
  • txnTaxDetail with any line missing taxCodeId (a sparse update replaces the whole Line array, which would strand a stale TxnTaxDetail);
  • a line taxCodeId without txnTaxDetail;
  • a totalTax larger than the sum of the line amounts.

totalTax must be a finite number ≥ 0, each address field must be a string or number, and each address needs at least line1. All three fields are optional and leave the request unchanged when absent. The Invoice.TxnTaxDetail.TotalTax returned by the write is then compared against what was submitted — both rounded to cents, so they must agree to within one cent — and the step fails on a mismatch rather than reporting a success QuickBooks did not honour. QuickBooks returns the persisted transaction in its create/update response, so a recompute shows up there and no second fetch is needed.

{
  "action": "quickbooks.createInvoice",
  "config": {
    "customerId": "{{steps.customer.Customer.Id}}",
    "lines": [{ "amount": 100, "itemId": "12", "taxCodeId": "TAX" }],
    "shipAddr": { "line1": "1 Main St", "city": "Austin", "countrySubDivisionCode": "TX", "postalCode": "78701", "country": "US" },
    "txnTaxDetail": { "totalTax": "{{steps.avalara.totalTax}}", "txnTaxCodeId": "5" }
  }
}

Intuit documents this override at create time. The update path of upsertInvoiceByDocNumber sends the same fields but has not been verified against a live company; a taxed invoice also cannot later be un-taxed through the upsert, because both fields are now required together.

getInvoice

Get an invoice by ID.

{
  "action": "quickbooks.getInvoice",
  "config": {
    "invoiceId": "{{input.invoiceId}}"
  }
}

sendInvoice

Send an invoice to the customer via email.

{
  "action": "quickbooks.sendInvoice",
  "config": {
    "invoiceId": "{{steps.create-invoice.output.Invoice.Id}}",
    "email": "{{input.alternateEmail}}"
  }
}

voidInvoice

Void an invoice.

{
  "action": "quickbooks.voidInvoice",
  "config": {
    "invoiceId": "{{input.invoiceId}}"
  }
}

updateInvoice

Update an existing invoice by its QuickBooks id. The request is sparse, so only the supplied fields change — except lines, which replaces the whole Line array (QuickBooks has no per-line patch).

{
  "action": "quickbooks.updateInvoice",
  "config": {
    "invoiceId": "{{input.invoiceId}}",
    "dueDate": "2024-03-01",
    "customerMemo": "Updated payment terms"
  }
}
FieldTypeRequiredDescription
invoiceIdstringYesQuickBooks invoice Id
linesarrayNoReplaces the entire Line array when supplied
lines[].taxCodeIdstringNoTax code (TAX, NON, or a tax-code id) → SalesItemLineDetail.TaxCodeRef; requires txnTaxDetail
dueDatestringNoDue date (YYYY-MM-DD)
customerMemostringNoMemo for customer
privateNotestringNoInternal note
emailstringNoBilling email
txnTaxDetailobjectNo{ totalTax, txnTaxCodeId } — both required → TxnTaxDetail; requires lines
shipAddrobjectNo{ line1, line2?, city?, countrySubDivisionCode?, postalCode?, country? } → ShipAddr
billAddrobjectNoSame shape → BillAddr

Because the sparse update replaces the whole Line array, an update that re-sends lines without taxCodeId and without txnTaxDetail would drop tax a previous create attached and let Automated Sales Tax recompute it.

Four of the rules below are createInvoice's coupling rules, and they run before the invoice is read, so bad input never costs a request:

  • txnTaxDetail with no lines is rejected — there would be no lines to code;
  • txnTaxDetail with any line missing taxCodeId is rejected;
  • a line taxCodeId with no txnTaxDetail is rejected;
  • a totalTax larger than the line total (compared in cents) is rejected.

Each of those four needs a tax field to be present, so on their own they do nothing about the case that actually loses the tax: an update carrying lines and no tax input at all. The fifth rule is the one that covers it, and it reads the stored invoice rather than the config:

  • supplying lines with no txnTaxDetail against an invoice that shows any sign of stored tax is rejected after the read and before the write. The error names the field it read and the remedy: resend txnTaxDetail together with a per-line taxCodeId, both recomputed for the new lines. This costs no extra request — the read that fetches the SyncToken returns the whole invoice.

Any one of these signals is enough, because QuickBooks does not populate them together:

SignalWhy it alone is not enough
TxnTaxDetail.TotalTax greater than zeroAn invoice can carry a TaxLine with no TotalTax key at all.
Any TxnTaxDetail.TaxLine[].Amount greater than zeroIntuit's non-US automated-sales-tax sample pairs a TaxLine of 3.33 with TotalTax: 0 (in the request; its sample response corrects TotalTax). A record that ever shows that pair must not read as untaxed.
Any stored line whose SalesItemLineDetail.TaxCodeRef.value is not NONCatches an invoice carrying tax-coded lines and no TxnTaxDetail.

The line-code rule reads the code's value, never the mere presence of TaxCodeRef: QuickBooks stamps NON on non-taxable lines, so keying on presence would treat every untaxed invoice as taxed. NON is compared case-insensitively, and it is the only value that means "no tax" — US companies get the TAX/NON pseudo-codes, non-US ones an opaque tax-code Id, so any other value has to read as a real code.

A TxnTaxDetail that is present but cannot be parsed into the above — not an object, a non-numeric TotalTax, a TaxLine that is not an array, a TaxLine entry with no readable Amount, a TaxCodeRef with no readable value — is also rejected, with an error saying the field could not be read. Ambiguity fails closed: the state at risk lives on the server, so a field the action cannot parse is not evidence that there is no tax.

GlobalTaxCalculation is deliberately not a signal. Intuit documents it as required for every non-US company and documents no value that means "no tax" (NotApplicable's trigger conditions are never explained and it appears in no sample), so keying on it would refuse every non-US line replacement whether or not the invoice is taxed. The shapes it would have caught are already covered: a TaxExcluded invoice with tax-coded lines by the line-code rule, a TaxInclusive one by its TaxLine.

What still goes through unchanged: an invoice with no TxnTaxDetail and no coded lines; one whose TotalTax is 0 with no positive TaxLine and every line coded NON; and any update that supplies full tax input.

An update with no lines is unaffected — a sparse body leaves Line alone, so the stored tax is not at risk and a metadata-only edit still goes through.

The same five rules govern the update path of upsertInvoiceByDocNumber, which also replaces the whole Line array — on both of its branches, whether the target was resolved from the caller's stored invoiceId or found by the DocNumber probe. Its create path has no stored tax to protect and is not affected.

The TotalTax returned by the update is checked against the submitted one, to the cent, exactly as on createInvoice. Supplying none of the tax or address fields against an untaxed invoice leaves the request byte-identical to what this action sent before they existed.

listInvoices

List invoices with optional filters.

{
  "action": "quickbooks.listInvoices",
  "config": {
    "customerId": "{{input.customerId}}",
    "balance": 0,
    "balanceOperator": ">",
    "txnDateAfter": "2024-01-01",
    "limit": 100
  }
}

findInvoiceByDocNumber

Look up an invoice by its DocNumber (the invoice number / reference no.). Returns a normalized result rather than the raw QueryResponse, because QuickBooks omits the entity array entirely when nothing matches.

{
  "action": "quickbooks.findInvoiceByDocNumber",
  "config": {
    "realmId": "{{input.realmId}}",
    "docNumber": "5314-5050 BL"
  }
}

Parameters:

FieldTypeRequiredDescription
realmIdstringYesQuickBooks company (realm) ID
docNumberstringYesInvoice number to look up

Output:

{
  "found": true,
  "id": "239",
  "syncToken": "0",
  "entity": { "...": "the matching Invoice object, or null when found is false" }
}

A zero-match lookup returns { "found": false, "id": null, "syncToken": null, "entity": null }.

upsertInvoiceByDocNumber

Create-or-update an invoice keyed on its DocNumber. The DocNumber probe runs first, so a rerun over the same period updates the invoice a previous attempt created instead of creating a duplicate — even when the caller no longer has the stored QuickBooks id. QuickBooks replaces the whole Line array on update, so every line must be supplied on every run.

{
  "id": "upsert-payroll-invoice",
  "type": "action",
  "action": "quickbooks.upsertInvoiceByDocNumber",
  "config": {
    "realmId": "{{input.realmId}}",
    "docNumber": "5314-5050 BL",
    "customerId": "{{steps.find-customer.output.id}}",
    "lines": [
      {
        "description": "Gross Payroll",
        "amount": 2496.27,
        "quantity": 1,
        "unitPrice": 2496.27,
        "itemId": "12"
      }
    ],
    "invoiceDate": "2026-07-15",
    "dueDate": "2026-08-14",
    "privateNote": "Payroll invoice for pay day 2026-07-15"
  }
}

Parameters:

FieldTypeRequiredDescription
realmIdstringYesQuickBooks company (realm) ID
docNumberstringYesInvoice number — the idempotency key
customerIdstringYesCustomer ID
linesarrayYesInvoice lines (at least one)
lines[].descriptionstringNoLine description
lines[].amountnumberYesLine amount
lines[].quantitynumberNoQuantity (default: 1)
lines[].unitPricenumberNoUnit price (default: the line amount)
lines[].itemIdstringNoProduct/Service ID
lines[].taxCodeIdstringNoTax code (TAX, NON, or a tax-code id) → SalesItemLineDetail.TaxCodeRef; requires txnTaxDetail
invoiceDatestringNoTransaction date, YYYY-MM-DD (default: today)
dueDatestringNoDue date (YYYY-MM-DD)
customerMemostringNoMemo for customer
privateNotestringNoInternal note
txnTaxDetailobjectNo{ totalTax, txnTaxCodeId } — both required → TxnTaxDetail; suppresses US Automated Sales Tax (see createInvoice)
shipAddrobjectNo{ line1, line2?, city?, countrySubDivisionCode?, postalCode?, country? } → ShipAddr
billAddrobjectNoSame shape → BillAddr

The three tax/address fields are sent on both the create and the update path. The four input-shape rejections listed under createInvoice run before the DocNumber probe, so bad input never costs a query, and on both paths the TotalTax returned by the write is checked against the submitted one, to the cent.

One further rejection is specific to the update path and necessarily runs after the probe: because this action always replaces the whole Line array, an update onto an invoice whose stored TxnTaxDetail.TotalTax is greater than zero is refused when no txnTaxDetail is supplied — the replacement lines would otherwise drop the tax and let Automated Sales Tax recompute it against a 200. The error names the stored tax; resend txnTaxDetail and a per-line taxCodeId recomputed for the new lines. The create path has no stored tax and is unaffected, as is an invoice stored with zero tax (a NON-coded line is not treated as taxed). See updateInvoice for the full rule set. That update path has still not been exercised against a live Automated Sales Tax company — see the note under createInvoice.

Output:

{
  "operation": "created",
  "invoiceId": "238",
  "syncToken": "0",
  "totalAmount": 100.0,
  "docNumber": "5314-5050 BL",
  "realmId": "9341454792746027",
  "Invoice": { "...": "the QuickBooks Invoice object from the write response" }
}

operation is "created" or "updated". syncToken and totalAmount are null when the response omits them.

Journal Entry Actions

A journal entry needs at least one debit line and one credit line, and the two columns must total the same amount. Each line names an account either by accountId (the QuickBooks Account Id) or by accountNumber (the account's number in the chart of accounts) — account numbers are resolved with a single chart-of-accounts query per call, and an unresolvable number fails the step.

createJournalEntry

Post a new journal entry. Returns the raw QuickBooks response.

{
  "id": "post-payroll-je",
  "type": "action",
  "action": "quickbooks.createJournalEntry",
  "config": {
    "realmId": "{{input.realmId}}",
    "docNumber": "5314-5050 BL",
    "txnDate": "2026-07-15",
    "privateNote": "Net payroll costs for pay day 2026-07-15",
    "journalLines": [
      {
        "accountNumber": "400001",
        "postingType": "debit",
        "amount": 1222.92,
        "description": "Net Payroll Costs"
      },
      {
        "accountId": "44",
        "postingType": "credit",
        "amount": 1222.92,
        "description": "Payroll Clearing"
      }
    ]
  }
}

Parameters:

FieldTypeRequiredDescription
realmIdstringYesQuickBooks company (realm) ID
journalLinesarrayYesDebit/credit distribution lines (at least one)
journalLines[].amountnumberYesLine amount
journalLines[].postingTypestringNodebit or credit — anything other than credit posts as a debit
journalLines[].accountIdstringNoQuickBooks Account Id (takes precedence)
journalLines[].accountNumberstringNoAccount number, resolved against the chart of accounts when accountId is absent
journalLines[].descriptionstringNoLine description
docNumberstringNoReference number for the entry
txnDatestringNoTransaction date, YYYY-MM-DD (default: today)
privateNotestringNoInternal note

The executor also accepts lines as an alias for journalLines, so hand-authored workflow JSON can use the same key as the invoice actions.

Output:

{
  "time": "2015-06-29T12:45:32.183-07:00",
  "JournalEntry": { "...": "the created QuickBooks JournalEntry object" }
}

findJournalEntryByDocNumber

Look up a journal entry by its DocNumber. Normalized the same way as findInvoiceByDocNumber.

{
  "action": "quickbooks.findJournalEntryByDocNumber",
  "config": {
    "realmId": "{{input.realmId}}",
    "docNumber": "5314-5050 BL"
  }
}

Parameters:

FieldTypeRequiredDescription
realmIdstringYesQuickBooks company (realm) ID
docNumberstringYesJournal entry reference number to look up

Output:

{
  "found": true,
  "id": "227",
  "syncToken": "1",
  "entity": { "...": "the matching JournalEntry object, or null when found is false" }
}

upsertJournalEntryByDocNumber

Create-or-update a journal entry keyed on its DocNumber. Idempotent by construction: the DocNumber probe runs first, so a retried step updates the entry a previous attempt created instead of posting a duplicate. Lines are always sent in full because QuickBooks replaces the Line array wholesale on update.

{
  "id": "upsert-payroll-je",
  "type": "action",
  "action": "quickbooks.upsertJournalEntryByDocNumber",
  "config": {
    "realmId": "{{input.realmId}}",
    "docNumber": "5314-5050 BL",
    "txnDate": "2026-07-15",
    "privateNote": "Net payroll costs for pay day 2026-07-15",
    "journalLines": [
      {
        "accountNumber": "400001",
        "postingType": "debit",
        "amount": 1222.92,
        "description": "Net Payroll Costs"
      },
      {
        "accountId": "44",
        "postingType": "credit",
        "amount": 1222.92,
        "description": "Payroll Clearing"
      }
    ]
  }
}

Parameters:

FieldTypeRequiredDescription
realmIdstringYesQuickBooks company (realm) ID
docNumberstringYesReference number — the idempotency key
journalLinesarrayYesDebit/credit distribution lines (at least one)
journalLines[].amountnumberYesLine amount
journalLines[].postingTypestringNodebit or credit — anything other than credit posts as a debit
journalLines[].accountIdstringNoQuickBooks Account Id (takes precedence)
journalLines[].accountNumberstringNoAccount number, resolved against the chart of accounts when accountId is absent
journalLines[].descriptionstringNoLine description
txnDatestringNoTransaction date, YYYY-MM-DD (default: today)
privateNotestringNoInternal note

Output:

{
  "operation": "created",
  "journalEntryId": "228",
  "syncToken": "0",
  "docNumber": "5314-5050 BL",
  "realmId": "9341454792746027",
  "JournalEntry": { "...": "the QuickBooks JournalEntry object from the write response" }
}

operation is "created" or "updated". syncToken is null when the response omits it.

Payment Actions

createPayment

Record a customer payment. invoiceIds accepts one invoice; the full payment amount is allocated to it. For multiple invoices, use Batch with a Payment create operation and explicit Line amounts for each allocation.

{
  "id": "record-payment",
  "type": "action",
  "action": "quickbooks.createPayment",
  "config": {
    "customerId": "{{input.customerId}}",
    "amount": 1500.00,
    "paymentDate": "2024-01-20",
    "paymentMethodId": "1",
    "invoiceIds": ["{{input.invoiceId}}"],
    "referenceNumber": "CHK-12345"
  }
}

Parameters:

FieldTypeRequiredDescription
customerIdstringYesCustomer ID
amountnumberYesPayment amount
paymentDatestringNoPayment date (default: today)
paymentMethodIdstringNoPayment method reference
depositAccountIdstringNoAccount to deposit to
invoiceIdsarrayNoInvoices to apply payment to
referenceNumberstringNoCheck/reference number

getPayment

Get a payment by ID.

{
  "action": "quickbooks.getPayment",
  "config": {
    "paymentId": "{{input.paymentId}}"
  }
}

voidPayment

Void a payment.

{
  "action": "quickbooks.voidPayment",
  "config": {
    "paymentId": "{{input.paymentId}}"
  }
}

Bill (AP) Actions

createBill

Create a vendor bill (accounts payable).

{
  "id": "create-bill",
  "type": "action",
  "action": "quickbooks.createBill",
  "config": {
    "vendorId": "{{input.vendorId}}",
    "lines": [
      {
        "description": "Office Supplies",
        "amount": 250.00,
        "accountId": "54",
        "billableStatus": "NotBillable"
      }
    ],
    "dueDate": "2024-02-28",
    "billNumber": "BILL-001"
  }
}

getBill / updateBill / deleteBill

{
  "action": "quickbooks.getBill",
  "config": { "billId": "{{input.billId}}" }
}

Vendor Actions

createVendor

Create a new vendor.

{
  "action": "quickbooks.createVendor",
  "config": {
    "displayName": "{{input.vendorName}}",
    "companyName": "{{input.companyName}}",
    "email": "{{input.email}}",
    "is1099": true,
    "taxId": "{{input.taxId}}"
  }
}

Account Actions

getAccountList

Get the chart of accounts.

{
  "action": "quickbooks.getAccountList",
  "config": {
    "active": true,
    "accountType": "Expense",
    "classification": "Expense"
  }
}

Account Types: Bank, Accounts Receivable, Other Current Asset, Fixed Asset, Accounts Payable, Credit Card, Other Current Liability, Long Term Liability, Equity, Income, Cost of Goods Sold, Expense, Other Income, Other Expense

createAccount

Create a new account in the chart of accounts.

{
  "action": "quickbooks.createAccount",
  "config": {
    "name": "Software Subscriptions",
    "accountType": "Expense",
    "accountSubType": "SubscriptionFees",
    "description": "SaaS and software subscriptions"
  }
}

Journal Entry Actions

Journal entries post directly to the general ledger. Every line carries an account, an amount, and a posting side; total debits must equal total credits. The executors validate that balance before calling QuickBooks, so an unbalanced entry fails with an explicit message instead of a generic QuickBooks fault.

accountId is the QuickBooks internal account Id, not the account number. Resolve numbers to Ids first with getAccountList or query (SELECT Id, Name, AcctNum FROM Account) — Ids differ between QuickBooks companies.

createJournalEntry

Create a journal entry.

{
  "id": "post-journal-entry",
  "type": "action",
  "action": "quickbooks.createJournalEntry",
  "config": {
    "txnDate": "2026-07-17",
    "docNumber": "5322-5217 JE",
    "privateNote": "Payroll passthrough",
    "lines": [
      {
        "accountId": "92",
        "amount": 1222.92,
        "postingType": "Debit",
        "description": "Net payroll costs"
      },
      {
        "accountId": "33",
        "amount": 1222.92,
        "postingType": "Credit",
        "description": "Payroll clearing"
      }
    ]
  }
}

Line fields: accountId (required), amount (required, non-negative), postingType (required, Debit or Credit), description, entityType (Customer / Vendor / Employee) with entityId, classId, departmentId.

Limits: at least two lines, with at least one debit and one credit; docNumber is capped at 21 characters by QuickBooks; the balance check tolerates rounding differences up to $0.005. Omit txnDate to let QuickBooks date the entry with the company's own today — passing the caller's date is only correct if it is already the intended accounting day.

Accounts Receivable / Accounts Payable lines require an entity. QuickBooks rejects a journal entry line posting to an AR account without a Customer, or to an AP account without a Vendor, with a generic business-validation fault — a common cause of a failed create. Set entityType and entityId on those lines.

getJournalEntry

Fetch a journal entry by its QuickBooks Id.

{
  "action": "quickbooks.getJournalEntry",
  "config": {
    "journalEntryId": "{{steps.post-journal-entry.JournalEntry.Id}}"
  }
}

updateJournalEntry

Update a journal entry. The current SyncToken is read automatically before the write. Omitted fields are preserved; supplying lines replaces the entry's lines wholesale (QuickBooks has no per-line patch) and re-validates the balance.

{
  "action": "quickbooks.updateJournalEntry",
  "config": {
    "journalEntryId": "148",
    "privateNote": "Corrected workers comp margin",
    "lines": [
      { "accountId": "92", "amount": 100.0, "postingType": "Debit" },
      { "accountId": "33", "amount": 100.0, "postingType": "Credit" }
    ]
  }
}

deleteJournalEntry

Delete a journal entry. QuickBooks offers no void operation for journal entries — delete is the only reversal.

{
  "action": "quickbooks.deleteJournalEntry",
  "config": {
    "journalEntryId": "148"
  }
}

listJournalEntries

List journal entries, optionally filtered by document number or transaction date range.

{
  "action": "quickbooks.listJournalEntries",
  "config": {
    "txnDateAfter": "2026-07-01",
    "txnDateBefore": "2026-07-31",
    "limit": 100
  }
}

findJournalEntryByDocNumber

Check whether a journal entry with a given document number already exists — the duplicate guard to run before posting. Returns { exists, count, journalEntries } rather than the raw QuickBooks envelope, so a condition step can branch on exists directly.

{
  "action": "quickbooks.findJournalEntryByDocNumber",
  "config": {
    "docNumber": "5322-5217 JE"
  }
}

Report Actions

runReport

Run a financial report.

{
  "id": "monthly-pnl",
  "type": "action",
  "action": "quickbooks.runReport",
  "config": {
    "reportType": "ProfitAndLoss",
    "startDate": "2024-01-01",
    "endDate": "2024-01-31",
    "accountingMethod": "Accrual",
    "summarizeBy": "Month"
  }
}

The searchable Report selector supports these Intuit endpoint names:

AccountList, AgedPayableDetail, AgedPayables, AgedReceivableDetail, AgedReceivables, BalanceSheet, CashFlow, CustomerBalance, CustomerBalanceDetail, CustomerIncome, GeneralLedger, InventoryValuationDetail, InventoryValuationSummary, JournalReport, ProfitAndLoss, ProfitAndLossDetail, ClassSales, CustomerSales, DepartmentSales, ItemSales, TaxSummary, TransactionList, TransactionListByVendor, TransactionListByCustomer, TransactionListWithSplits, TrialBalanceFR, TrialBalance, VendorBalance, VendorBalanceDetail, VendorExpenses.

Use reportParams for report-specific scalar query parameters, such as {"account":"1,2","summarize_column_by":"Month"}. These override the equivalent dedicated fields. Unsupported filters and reports return Intuit's error for that company.

The response preserves Header, Columns, and Rows, including nested sections and summaries. To analyze the report, add an Agent or Code step after monthly-pnl and reference {{steps.monthly-pnl.output.Header}}, {{steps.monthly-pnl.output.Columns}}, and {{steps.monthly-pnl.output.Rows}}. Keep column metadata with the rows so the analysis can distinguish labels, periods, and amounts. Chat can create the same workflow, for example: "Run January's Profit and Loss report in QuickBooks, then analyze expenses using the report's columns and rows."

Company Actions

getCompanyInfo

Read the connected company's profile. Use it as a pre-flight guard: a workflow can assert, before writing anything, that the (connection, realmId) pair it was handed really is the company it expects.

{
  "id": "assert-company",
  "type": "action",
  "action": "quickbooks.getCompanyInfo",
  "config": {
    "realmId": "{{input.realmId}}"
  }
}

Parameters:

FieldTypeRequiredDescription
realmIdstringYesQuickBooks company (realm) ID

Output:

{
  "realmId": "9341454792746027",
  "companyName": "Larry's Bakery",
  "legalName": "Larry's Bakery",
  "CompanyInfo": { "...": "the full QuickBooks CompanyInfo object" }
}

companyName and legalName are null when the response omits them.

Query Action

query

Execute a custom QuickBooks query.

{
  "action": "quickbooks.query",
  "config": {
    "query": "SELECT * FROM Invoice WHERE Balance > '0' ORDER BY TxnDate DESC MAXRESULTS 50"
  }
}

Webhook Triggers

QuickBooks webhooks can trigger workflows on data changes.

{
  "trigger": {
    "type": "webhook",
    "provider": "quickbooks",
    "events": ["Invoice", "Customer", "Payment"]
  }
}

Event Types: Customer, Invoice, Payment, Bill, Vendor, Account, Purchase, SalesReceipt, Estimate

Example Workflow

Complete invoice workflow triggered by Stripe payment:

{
  "name": "Stripe to QuickBooks Invoice",
  "trigger": {
    "type": "webhook",
    "provider": "stripe",
    "events": ["invoice.paid"]
  },
  "steps": [
    {
      "id": "find-customer",
      "type": "action",
      "action": "quickbooks.findCustomerByEmail",
      "config": {
        "email": "{{input.data.object.customer_email}}"
      }
    },
    {
      "id": "check-customer",
      "type": "condition",
      "config": {
        "conditions": {
          "left": "{{steps.find-customer.output.QueryResponse.Customer}}",
          "operator": "exists"
        },
        "then": ["create-invoice"],
        "else": ["create-customer"]
      }
    },
    {
      "id": "create-customer",
      "type": "action",
      "action": "quickbooks.createCustomer",
      "config": {
        "displayName": "{{input.data.object.customer_name}}",
        "email": "{{input.data.object.customer_email}}"
      }
    },
    {
      "id": "create-invoice",
      "type": "action",
      "action": "quickbooks.createInvoice",
      "config": {
        "customerId": "{{steps.find-customer.output.QueryResponse.Customer[0].Id || steps.create-customer.output.Customer.Id}}",
        "lines": [
          {
            "description": "{{input.data.object.lines.data[0].description}}",
            "amount": "{{input.data.object.amount_paid / 100}}"
          }
        ]
      }
    },
    {
      "id": "record-payment",
      "type": "action",
      "action": "quickbooks.createPayment",
      "config": {
        "customerId": "{{steps.find-customer.output.QueryResponse.Customer[0].Id || steps.create-customer.output.Customer.Id}}",
        "amount": "{{input.data.object.amount_paid / 100}}",
        "invoiceIds": ["{{steps.create-invoice.output.Invoice.Id}}"],
        "referenceNumber": "{{input.data.object.id}}"
      }
    }
  ]
}

Rate Limits

LimitValue
API calls per minute500
Batch operations30 records per batch
Query results1000 records max
Concurrent connections10

Troubleshooting

Common Errors

ErrorCauseSolution
realmId is requiredMissing company IDSet config.realmId on the step (the block's Company (Realm) ID field)
SyncToken mismatchConcurrent updatesRefetch record before update
Business validation errorInvalid dataCheck field formats and requirements
ThrottledRate limit exceededImplement exponential backoff

Configuration: realmId

Every QuickBooks action needs realmId (the company ID) to build its request path, and step config is the standard way to supply it. Set config.realmId on every QuickBooks step:

{
  "action": "quickbooks.getCompanyInfo",
  "config": {
    "realmId": "9341454792746027"
  }
}

On the canvas this is the Company (Realm) ID field on the QuickBooks block, which is required. A step without it fails with realmId is required for QuickBooks API calls.

The action resolver also accepts trusted variables.realmId as a fallback for runtime callers. The canvas does not automatically populate this value from the selected connection; set the Company (Realm) ID explicitly when authoring a workflow.

On this page