> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asteragents.com/llms.txt
> Use this file to discover all available pages before exploring further.

# QuickBooks Online

> Connect QuickBooks Online to your Agent

## Overview

The QuickBooks Online integration lets your agents query the accounting data in your QBO company file — customers, vendors, invoices, bills, payments, and the chart of accounts — and run financial reports like Profit & Loss, Balance Sheet, Cash Flow, and AR/AP aging.

The connection is **org-level**: any agent in your workspace can use the QuickBooks tools once an admin has connected the integration. Unlike most other integrations in Aster, QuickBooks uses a **one-click OAuth flow** — admins click Connect, authorize via Intuit, and they're done. No API keys to copy or refresh tokens to manage.

## Features

* **One-click OAuth setup** — click Connect, authorize via Intuit, done. No API keys to manage.
* **Query language** — run QBO `SELECT` statements with automatic pagination across thousands of records.
* **Schema discovery** — agents learn QuickBooks entities and field names at runtime via `quickbooks_get_schema`.
* **Full CRUD writes** — create, update, delete, and void records across customers, vendors, invoices, bills, payments, journal entries, and more.
* **Financial reports** — pull P\&L, Balance Sheet, AR aging, AP aging, Customer/Vendor balances, Item Sales, Trial Balance, General Ledger, and \~25 other QBO reports with date macros (`This Fiscal Year-to-date`, `Last Quarter`, etc.).
* **Automatic token refresh** — access tokens expire every 60 minutes; we refresh them transparently with no agent involvement.
* **Connection management modal** — see when the integration was connected, view the QuickBooks Company ID, and track when authorization expires.

## Prerequisites

* A QuickBooks Online subscription (any tier — Simple Start, Essentials, Plus, or Advanced).
* An Aster organization admin role to set up the integration. Once connected, every member of the org can use it through their agents.
* The Intuit account used to authorize the connection must have access to the QuickBooks company you want to expose.

## Setup Guide

<Steps>
  <Step title="Open Control Hub > Integrations">
    In Aster Agents, navigate to **Control Hub > Integrations** and locate the QuickBooks Online card.
  </Step>

  <Step title="Click Connect">
    Click **Connect** on the QuickBooks card. A modal will appear explaining the OAuth flow. Click **Continue to QuickBooks Online** to be redirected to Intuit.
  </Step>

  <Step title="Sign in to Intuit">
    Sign in with the Intuit account that has access to the QuickBooks company you want to connect. **The QBO company you select on the Intuit consent screen is the one Aster will read from** — make sure you pick the right one if you have access to multiple.
  </Step>

  <Step title="Authorize Aster">
    Approve the authorization request. Intuit will redirect you back to Aster, where you'll see a "QuickBooks connected successfully" confirmation toast and the integration card will flip to **Connected**.
  </Step>

  <Step title="Add Tools to Your Agent">
    Edit any agent and enable the QuickBooks tools you want it to have:

    * **QuickBooks Get Schema** — discover entity and field names
    * **QuickBooks Query** — run `SELECT` queries against QBO entities
    * **QuickBooks Get Report** — run financial reports (P\&L, Balance Sheet, etc.)
    * **QuickBooks Manage Records** — create, update, delete, or void records

    We recommend enabling Get Schema, Query, and Get Report on every QuickBooks agent. Add Manage Records when the agent needs to write — for example, agents that create invoices from inbound emails, automate AP workflows, or post month-end journal entries.
  </Step>
</Steps>

## Available Tools

### QuickBooks Query

Run QBO query language `SELECT` statements against any entity. Auto-paginates up to 10 pages × 1000 rows; pass `auto_paginate: false` if you've added your own `STARTPOSITION` / `MAXRESULTS`.

```sql theme={null}
SELECT Id, DisplayName, Balance
FROM Customer
WHERE Active = true
ORDER BY Balance DESC
```

```sql theme={null}
SELECT Id, DocNumber, TxnDate, TotalAmt, CustomerRef
FROM Invoice
WHERE TxnDate >= '2026-01-01'
ORDER BY TxnDate DESC
```

```sql theme={null}
SELECT COUNT(*) FROM Bill WHERE Balance > 0
```

QBO query language is similar to SQL but **does not support `JOIN`s, `GROUP BY`, or arbitrary aggregates** — `COUNT(*)` is the only aggregate. Most analytical queries should be done by pulling the rows and aggregating client-side, or by using **QuickBooks Get Report** for built-in financial reports.

### QuickBooks Get Schema

Discover available entities and their fields. Call without arguments to list every supported entity, or pass an entity name to get the field list and example queries.

Currently surfaces these entities:
**Customer**, **Vendor**, **Invoice**, **Bill**, **Payment**, **BillPayment**, **Account**, **Item**, **Estimate**, **PurchaseOrder**, **Class**, **Department**, **CompanyInfo**.

For entities not in the curated list (e.g. `JournalEntry`, `CreditMemo`, `Deposit`, `Transfer`), you can still query them directly via `quickbooks_query` — see the [Intuit API reference](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities) for the full list.

### QuickBooks Get Report

Run any of QuickBooks' built-in financial reports. Each returns a structured rows-and-columns response.

Supported reports include:

* **P\&L**: `ProfitAndLoss`, `ProfitAndLossDetail`
* **Balance Sheet**: `BalanceSheet`, `BalanceSheetDetail`
* **Cash flow**: `CashFlow`
* **GL / Trial balance**: `TrialBalance`, `GeneralLedger`, `JournalReport`
* **AR / AP aging**: `AgedReceivables`, `AgedReceivableDetail`, `AgedPayables`, `AgedPayableDetail`
* **Customer / Vendor**: `CustomerSales`, `CustomerIncome`, `CustomerBalance`, `CustomerBalanceDetail`, `VendorBalance`, `VendorBalanceDetail`, `VendorExpenses`
* **Inventory / Sales**: `ItemSales`, `InventoryValuationSummary`
* **Other**: `TransactionList`, `TaxSummary`, `DepartmentSales`, `ClassSales`

Date ranges can be specified with `start_date` + `end_date` (YYYY-MM-DD) or with `date_macro`:

* `Today`, `This Week`, `This Month`, `This Quarter`, `This Fiscal Year`, `This Fiscal Year-to-date`
* `Yesterday`, `Last Week`, `Last Month`, `Last Quarter`, `Last Fiscal Year`

Optional filters: `accounting_method` (`Cash` or `Accrual`), `summarize_column_by` (`Total`, `Month`, `Quarter`, `Year`, `Customers`, `Vendors`, `Classes`, `Departments`), and IDs for `customer`, `vendor`, `class`, `department`.

### QuickBooks Manage Records

Create, update, delete, or void records in QuickBooks. Single tool with an `action` enum covering full CRUD plus `void` (which is preferred over `delete` for transactions that have already affected reports).

**Supported entities:** Customer, Vendor, Invoice, Bill, Payment, BillPayment, SalesReceipt, CreditMemo, Deposit, Transfer, JournalEntry, Item, Estimate, PurchaseOrder, Account, Class, Department.

**Actions:**

* `get` — fetch a record by `Id` (useful for inspecting field values + the current `SyncToken` before an update)
* `create` — insert a new record. Pass `properties` containing the QBO fields per the API reference (e.g. an Invoice needs `CustomerRef` and `Line`).
* `update` — sparse update by `record_id`. Only fields you pass in `properties` are changed; everything else is preserved.
* `delete` — hard-delete a record by `record_id`. Permanent. Some entities (Accounts with transactions, etc.) refuse to delete.
* `void` — for transactions only (Invoice, Bill, Payment, etc.). Keeps the record but zeros it out. Preferred over `delete` for ledger entries that have already affected reports.

**SyncTokens (concurrency control):** QuickBooks uses an optimistic-concurrency `SyncToken` on every record. The tool fetches the current token automatically when you call `update`, `delete`, or `void`, so the agent doesn't need to track it. If you already have a fresh token (e.g. from a recent `get`), pass it as `sync_token` to skip the extra round-trip.

```json theme={null}
{
  "action": "create",
  "entity": "Invoice",
  "properties": {
    "CustomerRef": { "value": "12" },
    "Line": [
      {
        "DetailType": "SalesItemLineDetail",
        "Amount": 500,
        "SalesItemLineDetail": { "ItemRef": { "value": "5" } }
      }
    ]
  }
}
```

```json theme={null}
{
  "action": "update",
  "entity": "Customer",
  "record_id": "8",
  "properties": { "PrimaryEmailAddr": { "Address": "new@example.com" } }
}
```

```json theme={null}
{ "action": "void", "entity": "Invoice", "record_id": "104" }
```

## Managing Your Connection

Click the QuickBooks card again any time after connecting to open the management modal. From there you can:

* **See the connected Company ID** (Intuit's `realmId`)
* **See when the connection was created** and how long until your authorization expires
* **Reconnect** — refresh the OAuth grant (useful if you want to switch QBO companies, or your refresh token is approaching expiry)
* **Disconnect** — revoke Aster's access to your QuickBooks company

## Token Lifecycle

* **Access tokens** are valid for 60 minutes. Aster refreshes them automatically when they expire — no action required from you.
* **Refresh tokens** are valid for \~100 days from the time the connection was created. As long as at least one agent uses the integration within that window, the refresh process renews the lifetime. If a connected QuickBooks integration sits unused for 100+ days, you'll need to click **Reconnect** to re-authorize.

The management modal shows a warning when the refresh token is within 14 days of expiring.

## Permissions and Scope

Aster requests the **`com.intuit.quickbooks.accounting`** scope when connecting, which is QuickBooks' standard accounting scope. The consent screen will indicate Aster can read and write accounting data — both capabilities are exposed through the read tools (Query, Get Schema, Get Report) and the write tool (Manage Records). If you want an agent to be read-only, simply don't enable Manage Records on it.

## Security Considerations

* **Admin-only setup**: only organization admins can connect, reconnect, or disconnect QuickBooks. Regular members can use the tools through agents but cannot change the connection.
* **Tokens are stored at the org level** in the `connections` JSONB on the `organization_data` table. They never leave the server in API responses (the API redacts them as `[REDACTED]`).
* **OAuth state is HMAC-signed** with a dedicated `OAUTH_STATE_SECRET` to prevent CSRF and replay attacks during the consent flow.
* **Use a dedicated QBO user account** (not the company owner) if you want to scope agent access. QuickBooks role-based permissions on the connecting user apply to everything Aster can read or write.
* **Revoke access at Intuit** if you ever need to forcibly disconnect Aster from outside Aster: sign in to QuickBooks Online, go to **Settings > Manage your Intuit Account > Apps**, and remove the Aster Agents app.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Connection failed with 'invalid_state'">
    The signed state token has expired (10-minute TTL). Click Connect again — this typically happens if you opened the consent screen, walked away, and came back later. The flow has to complete in one sitting.
  </Accordion>

  <Accordion title="Connection failed with 'token_exchange_failed'">
    Intuit refused to exchange the authorization code for tokens. The most common causes are:

    * **Wrong redirect URI** registered in the Intuit Developer app — verify your prod redirect URI exactly matches the one Aster sends (`https://www.asteragents.com/api/integrations/quickbooks/callback`).
    * **Aster Intuit app credentials are misconfigured** in Vercel environment (`QBO_CLIENT_ID`, `QBO_CLIENT_SECRET`). Contact your Aster admin if this persists.
  </Accordion>

  <Accordion title="QBO query returns 'Invalid context declaration'">
    The entity name in your query is wrong. Use **QuickBooks Get Schema** to see the supported entity list, or check the [Intuit API reference](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities) for entities not in our curated catalog.
  </Accordion>

  <Accordion title="Empty results when there should be data">
    QBO query language is **case-sensitive** for entity and field names — `SELECT * FROM customer` returns nothing, but `SELECT * FROM Customer` works. Field names also must match exactly (e.g. `DisplayName`, not `displayname` or `display_name`).

    Also note that some QBO companies record income via Sales Receipts or Deposits rather than Invoices. If `SELECT * FROM Invoice` returns empty but the P\&L shows revenue, try `SELECT * FROM SalesReceipt` instead.
  </Accordion>

  <Accordion title="Refresh token expired — connection broken">
    QuickBooks refresh tokens expire after 100 days of disuse. Click **Reconnect** in the management modal and re-authorize via Intuit. Existing agents will start working again immediately.
  </Accordion>

  <Accordion title="Wrong QuickBooks company connected">
    The QBO company you bind is determined by which company you select on the Intuit consent screen. Click **Reconnect** in the management modal and pick a different company on the Intuit page.
  </Accordion>

  <Accordion title="`delete` on Customer/Vendor/Item silently soft-deletes (or fails)">
    QuickBooks does **not** allow hard deletion of master-data entities (Customer, Vendor, Item, Account, Class, Department, Employee). The QBO API rejects `action: "delete"` on these — instead, do `action: "update"` with `properties: { Active: false }` to soft-delete. QBO automatically suffixes the DisplayName with `(deleted)` and hides the record from default lists; it remains visible under "Inactive" filters and can be re-enabled by setting `Active: true` again. **Hard delete is not recoverable** — by contrast, transactions like Invoice / Bill / Payment can be hard-deleted but are usually safer to `void` (preserves the audit trail at \$0).
  </Accordion>

  <Accordion title="`update` fails with 'You cannot modify a list element that has been deleted'">
    The record (or a sub-element like an address or email) was soft-deleted between when you read its SyncToken and when your update arrived. Two common causes:

    * **Concurrent agent run** — another agent or workflow deactivated the record. Re-fetch with `action: "get"` and retry.
    * **Customer/Vendor that was already `Active: false`** — QBO rejects updates to most fields on inactive records. Reactivate first via `update` with `Active: true`.
  </Accordion>

  <Accordion title="`update` fails with stale SyncToken">
    QuickBooks uses optimistic concurrency: each record has a `SyncToken` that increments on every write. If two agents read the same record and both try to write, the second one gets rejected. The Manage Records tool auto-fetches the current SyncToken when you don't provide one — but if you cached it manually and a concurrent write landed in between, you'll get a 400 from QBO. Re-fetch via `action: "get"` and retry.
  </Accordion>
</AccordionGroup>

## Related

* [Intuit API reference — all entities](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities)
* [Intuit API reference — reports](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/reports)
* [QuickBooks query language reference](https://developer.intuit.com/app/developer/qbo/docs/learn/explore-the-quickbooks-online-api/data-queries)
