# Incremental sync
Source: https://www.desktopaccountingapi.com/docs/guides/incremental-sync/

> Keep your copy of QuickBooks data current with updatedAfter watermarks, deleted-object lists, and time windows for large files.

Most integrations copy part of a customer's QuickBooks data into their own database and keep it current. QuickBooks Desktop has no change feed, but every record carries an `updatedAt` timestamp and QuickBooks keeps a list of recent deletions. Together they give you a reliable incremental sync.

## The pattern

1. **First sync.** List each object type you need without a date filter, or in date windows for large files (see below). Store each record by its QuickBooks `id`, with its `revisionNumber` and `updatedAt`.
2. **Remember a watermark** per end user and object type: the time you started the sync, as QuickBooks sees it.
3. **Each later sync.** List with `updatedAfter` set to the last watermark, minus a small overlap. Upsert by `id`.
4. **Deletions.** Call `GET /v1/quickbooks-desktop/deleted-transactions` and `GET /v1/quickbooks-desktop/deleted-list-objects` with `deletedAfter` set to the same watermark, and remove those records.

**TypeScript**

```ts
import { DesktopAccountingApi } from "@desktopaccountingapi/quickbooks-desktop";

const qb = new DesktopAccountingApi().forEndUser("eu_01j9...");
const lastWatermark = "2026-10-04T00:00:00"; // from your database

for await (const invoice of qb.qbd.invoices.list({ updatedAfter: lastWatermark })) {
  // upsert by invoice.id
  console.log(invoice.id, invoice.revisionNumber);
}

const deleted = await qb.qbd.deletedTransactions.list({
  transactionTypes: ["invoice"],
  deletedAfter: lastWatermark,
});
for (const d of deleted.data) {
  // delete your copy of d.id
  console.log(d.id);
}
```

**Python**

```python
from desktopaccountingapi import DesktopAccountingApi

qb = DesktopAccountingApi().for_end_user("eu_01j9...")
last_watermark = "2026-10-04T00:00:00"  # from your database

for invoice in qb.qbd.invoices.list(updated_after=last_watermark):
    print(invoice.id, invoice.revision_number)  # upsert by id

deleted = qb.qbd.deleted_transactions.list(transaction_types=["invoice"], deleted_after=last_watermark)
for d in deleted.data:
    print(d.id)  # delete your copy
```

## Picking the watermark

QuickBooks timestamps use the clock and time zone of the customer's computer, for example `2026-10-05T09:14:03-07:00`. Filters work on the same clock:

- `updatedAfter` and `updatedBefore` accept a date (`2026-10-04`, meaning the start or end of that day), a local date-time without offset (`2026-10-04T09:00:00`, read in the computer's time zone), or a date-time with offset (`2026-10-04T16:00:00Z`, converted to the computer's time zone).
- Use the largest `updatedAt` you received as the next watermark, or the time the sync started minus a few minutes. Overlap a little and deduplicate by `id`; re-reading a record is harmless, missing one is not.
- `revisionNumber` changes on every edit. If the stored revision equals the new one, you can skip the record.

> **Note:**
> Around daylight saving changes, local timestamps repeat or skip an hour. An overlap of at least one hour on the watermark covers both cases.

## Deletions

| Endpoint | Covers | Filter |
| --- | --- | --- |
| `GET /v1/quickbooks-desktop/deleted-transactions` | Deleted invoices, bills, checks and other transactions | `transactionTypes` (required), `deletedAfter`, `deletedBefore` |
| `GET /v1/quickbooks-desktop/deleted-list-objects` | Deleted customers, vendors, items, accounts and other list objects | `objectTypes` (required), `deletedAfter`, `deletedBefore` |

QuickBooks keeps deletions for 90 days. Run incremental syncs more often than that, and do a full comparison by ID if a customer has been offline longer.

List objects (customers, items, vendors) are usually deactivated rather than deleted. Include inactive records with `status: "all"` so a deactivation shows up as `isActive: false`.

## Large company files

A single paged query must finish inside one QuickBooks session (see [Pagination](https://www.desktopaccountingapi.com/docs/guides/pagination/#why-cursors-expire)). For a first sync of a large file, split the range into windows:

1. Choose a window, such as one month of `updatedAfter` / `updatedBefore`, or one month of `transactionDateFrom` / `transactionDateTo` for transactions.
2. Read each window completely before moving on.
3. If a window fails with `CURSOR_EXPIRED`, retry that window, or split it in half.

For several end users, run their syncs in parallel; each company file has its own queue.

## Cross-type transactions

`GET /v1/quickbooks-desktop/transactions` returns a summary of every transaction type in one paged list, filtered by date, account, entity or type. Use it to find what changed across all types, then fetch full records from the type-specific endpoints.

## When to sync

Customer-started syncs ("Sync with QuickBooks" buttons) give the best experience, because a person is there to fix a connection problem. Scheduled background syncs work too: run them with async requests, record failures per customer, and show the last sync result in your product. Avoid tight polling loops; one sync every few minutes is plenty for most products, and each request waits its turn in the company file's single queue.
