Skip to content
Desktop Accounting API

Updates and line items

QuickBooks Desktop updates follow two rules that surprise people: every update must prove it saw the latest version, and sending a line array replaces all lines of that kind. Both prevent quiet data loss in a file that people also edit by hand.

Action Request Success
Create POST /v1/quickbooks-desktop/{resource} 201 with the new object
Retrieve GET /v1/quickbooks-desktop/{resource}/{id} 200
Update POST /v1/quickbooks-desktop/{resource}/{id} 200 with the updated object
Delete (transactions) DELETE /v1/quickbooks-desktop/{resource}/{id} 200 with { "id", "objectType", "refNumber", "deleted": true }
Void (16 transaction types) POST /v1/quickbooks-desktop/{resource}/{id}/void 200 with { "id", "objectType", "createdAt", "updatedAt", "refNumber", "voided": true }

There is no PUT or PATCH; updates are partial POSTs. List objects such as customers, vendors and items are not deleted through the typed API. Deactivate them with isActive: false, which is also what QuickBooks recommends once a record has history.

Every QuickBooks object has a revisionNumber that changes each time anyone edits it, through the API or in the QuickBooks window. An update must send the revisionNumber you last read:

POST /v1/quickbooks-desktop/customers/80000012-1730312001
{
"revisionNumber": "1730312567",
"phone": "555-0142"
}

If someone changed the customer since you read it, the update fails with 409 QBD_REVISION_NUMBER_STALE and nothing changes. Retrieve the record again, reapply your change to the fresh copy, and send the new revisionNumber. We never fetch the current revision for you, because that would overwrite the other person’s edit without anyone noticing.

import { DesktopAccountingApi, ApiError } from "@desktopaccountingapi/quickbooks-desktop";
const qb = new DesktopAccountingApi().forEndUser("eu_01j9...");
const id = "80000012-1730312001";
for (let attempt = 0; attempt < 3; attempt++) {
const current = await qb.qbd.customers.retrieve(id);
try {
await qb.qbd.customers.update(id, { revisionNumber: current.revisionNumber, phone: "555-0142" });
break;
} catch (err) {
if (err instanceof ApiError && err.code === "QBD_REVISION_NUMBER_STALE") continue;
throw err;
}
}
  • A field you leave out stays as it is.
  • A field you send is set to that value.
  • null clears a field, where QuickBooks allows clearing it. Clearable fields are nullable in the API reference. Sending null to any other field returns 400 FIELD_NOT_CLEARABLE.

In the SDKs, leaving a parameter out omits it, and passing null (None in Python) sends null.

Transactions with lines (invoices, bills, estimates, sales orders, sales receipts, credit memos, purchase orders, checks, journal entries and others) have one or more line arrays: lines and lineGroups on sales forms and purchase orders, expenseLines, itemLines and itemGroupLines on bills, checks, credit card charges and credits, item receipts and vendor credits, plus application lists on payments.

On update, each line array works as a complete replacement:

  • Omit the array to leave those lines untouched.
  • Send the array to replace all lines of that kind. Lines you do not include are deleted.
  • Keep a line by sending its id. Fields you send on it change; fields you omit keep their values. An id on its own leaves the line exactly as it is.
  • Add a line with "id": "-1".
POST /v1/quickbooks-desktop/invoices/1A4F-1730312567
{
"revisionNumber": "1730312567",
"lines": [
{ "id": "1A50-1730312567" },
{ "id": "1A51-1730312567", "quantity": 3 },
{ "id": "-1", "itemId": "80000007-1730311020", "quantity": 1, "rate": "45.00" }
]
}

This keeps the first line unchanged, changes the quantity on the second, removes any other existing ordinary lines, and adds a new line at the end. Any line groups on the invoice stay as they are, because lineGroups was left out.

QuickBooks itself rebuilds every line of a transaction as soon as an update touches any of them, and deletes whatever the update does not list, including line groups when you only sent lines. The API prevents that: when you send some line arrays of a transaction and leave out others, it first reads the transaction and sends each existing entry of the arrays you left out back to QuickBooks by id, so they are kept unchanged. You see this read as a separate request in the request log, just before the update.

Your revisionNumber still protects the update. If anyone changes the transaction between that read and the update, the update fails with 409 QBD_REVISION_NUMBER_STALE and nothing changes. If the read itself fails, for example because the connection is offline, the update is not sent and the error says so. The read happens before the API responds, also for Prefer: respond-async requests.

Ordinary lines are listed before line groups when you change lines, so a group that sat between ordinary lines on the form moves below them.

Some line fields can be set only when a line is created; for example, links to other transactions cannot be added to an existing line. The reference for each update operation lists its line input fields.

An item group expands into several lines on a form. Create one with a lineGroups entry that names the group item; QuickBooks adds its component lines. On output, lineGroups contains the group and its lines. To keep a group while you change other lines, leave lineGroups out or send the group’s id; to remove one group, send lineGroups without it.

Voiding keeps the transaction in the audit trail with zero amounts. Deleting removes it; deleted transactions stay visible for 90 days through GET /v1/quickbooks-desktop/deleted-transactions. Accountants usually prefer void for anything already sent to a customer. A few transaction types support only one of the two; see Supported environments.

Every create, update, delete and void accepts an Idempotency-Key, which the SDKs send for you. See Idempotency and safe retries.