multi-delete logo

Deleting transactions via the QuickBooks Online API, safely

The delete call, the batch behaviour, and the caveats that matter when the operation has no undo — written for the developer who is about to make these calls.

Short answer

There is no bulk-delete endpoint. Deletion is one record per call, and the only way to batch is the /batch endpoint at a hard 30 operations per request. Every delete carries the record’s SyncToken, which is supposed to reject stale writes — but in our testing the batch delete path did not enforce it, so the safe design re-reads and compares the token itself.

The delete call

One record, one call. The entity endpoint:

POST https://quickbooks.api.intuit.com/v3/company/{realmId}/invoice?operation=delete
Authorization: Bearer {access_token}
Content-Type: application/json

{ "Id": "123", "SyncToken": "0" }

There is also a generic transaction path that takes the same body and covers most transaction types:

POST https://quickbooks.api.intuit.com/v3/company/{realmId}/transaction?operation=delete
Authorization: Bearer {access_token}
Content-Type: application/json

{ "Id": "123", "SyncToken": "0" }

Which one to use depends on the entity — some types are documented under their own endpoint. Check Intuit’s current API reference for the specific entity before you build against it, and watch the minorversion you send. On the sandbox the base host is sandbox-quickbooks.api.intuit.com.

There is no bulk delete — so you batch

The reason there is no bulk endpoint is not technical. A delete with no undo, applied across thousands of records in one call, is exactly the thing a double-submit or a bad filter should not be able to do. So the API makes you enumerate: one record per operation, and you group those operations yourself with /batch.

Batching: 30 operations per call, and it is atomic

POST /v3/company/{realmId}/batch takes up to 30 BatchItemRequest entries. Exceed it and the whole call is rejected — in our sandbox a 31-operation batch returned fault 1040, “The Batch Limit is 30 BatchItemRequests”, with created: 0 — nothing applied. Not a partial success, a refusal.

Read the response per item. A batch returns a BatchItemResponse per operation; one bad operation does not fail the others, so a single status for the call is not enough to know what happened. If the call fails as a whole, the only way to find the poison entry is to retry the records one at a time.

SyncToken: optimistic concurrency — and a caveat we hit

Every QuickBooks record carries a SyncToken, a version stamp that increments as the record changes. The documented rule is absolute: every write, deletes included, must send the record’s current token, and a write based on a stale read is refused. That is optimistic concurrency, and it is what stops two people — or a person and a tool — silently clobbering each other.

Do not treat it as your safety net. In our sandbox testing, a batch delete of an invoice whose current token was "0", sent with "999", returned 200 Deleted. On the path a bulk tool actually uses, the token was not enforced. So if your requirement is “delete exactly the version I reviewed”, the API will not do that for you: re-read the record immediately before deleting it and compare the token against the one you captured when the user reviewed the set. If they differ, skip the record and report why. That comparison is your guard, and it deserves a unit test rather than one hand run.

Throttling and retries

Design a dry run before you design the delete

The API has no undo, so the shape of the tool matters more than the speed of it. The order that makes a destructive operation honest:

  1. Preview — a read-only query that returns the exact set, with what will be skipped and why.
  2. Forced export — the set as CSV or JSON, before anything is written. This is the only recovery path, so make it a hard gate, not an optional button.
  3. Dry run — per-record report of what would happen. Still nothing written.
  4. Delete — in batches, re-reading each record immediately before deleting it.
  5. Log — per record: deleted, skipped or failed, with the reason and the QuickBooks fault code. A log that only records successes is not a log.

Core and CorePlus — verify, do not assume

Intuit groups its API entities into categories (commonly described as Core and CorePlus) that affect which operations are available and how throttling is applied. The classification has been described differently over time and is not something to take from a blog post: check Intuit’s current developer documentation for the entity you intend to delete, and check it again before you build a pricing or rate-limit model on top of it. We flag it here rather than assert it, for the same reason we re-read every record before deleting it.

Questions developers actually ask

Is there a bulk delete endpoint in the QuickBooks Online API?

No. There is no single call that deletes many records. Deletion is one record per call; batching many operations into one HTTP request is done through the /batch endpoint, which caps at 30 operations per request. Any “bulk delete” is therefore your own loop over a batch call.

What is the delete call shape in the QuickBooks Online API?

A POST to the entity’s endpoint with the operation set to delete, carrying the record’s Id and current SyncToken as JSON. For example: POST /v3/company/{realmId}/invoice?operation=delete with a body of {"Id":"123","SyncToken":"0"} and a bearer access token. There is also a generic transaction path, POST /v3/company/{realmId}/transaction?operation=delete, which takes the same body and covers most transaction types.

How many operations can one batch call contain?

Thirty. Intuit documents a 30-operation limit for the /batch endpoint, and we confirmed it in the sandbox: a 31-operation batch was rejected with fault 1040, “The Batch Limit is 30 BatchItemRequests”, and the whole call was rejected atomically rather than partly applied. So plan your reads and deletes in chunks of 30.

What is a SyncToken and does it prevent stale deletes?

A SyncToken is a small version stamp on every record. Every write is supposed to send the record’s current token, so that a write based on an out-of-date read is refused — classic optimistic concurrency. Intuit documents that. But in our sandbox testing the /batch delete path accepted a mismatched token and returned success, so we do not treat the API as the guard: if you must delete exactly the version you reviewed, re-read the record immediately before the delete and compare the token yourself.

Why does my API delete fail, and what are the common fault codes?

The frequent ones: a delete dated inside a closed period is refused per record (we saw fault 6200, naming the book-closing date); more than 30 operations in a batch returns fault 1040; and rate limiting returns HTTP 429 with Intuit fault 3001 and a Retry-After header (60 seconds in our test). Check the fault code and message per item — a batch returns per-item results, not one status for the call.

How should a safe bulk delete be designed?

Preview the exact set with a read-only query, force an export of that set as the backup, run a dry run that reports what would happen per record, then delete in batches and produce a per-record log of deleted, skipped and failed with reasons. Because the API has no undo, the export is the only recovery path — so make it a hard gate the user cannot skip, and re-read each record immediately before deleting it.

Related: how to bulk delete transactions in QuickBooks Online · journal entries · invoices and recurring transactions.