Decoding QuickBooks Desktop Errors: qbXML Status Codes, HRESULTs, and QBO-Style Faults
- quickbooks desktop error 3140
- qbxml status code
- quickbooks online fault

Quick answers
- What is a qbXML status code? It's a numeric
statusCodeattribute returned on each response node in a qbXML message, where0means success and anything else describes what QuickBooks Desktop refused to do. - What does QuickBooks Desktop error 3140 mean?
3140is "there is an invalid reference to a QuickBooks list element" — you sent a ListID or FullName that doesn't exist in that company file. - How is that different from a QuickBooks Online fault? QBO returns an HTTP status plus a JSON
Faultobject with a code, message, and detail; qbXML returns HTTP 200 with an error buried in XML attributes. - Are HRESULTs the same as qbXML errors? No — HRESULTs like
0x80040408come from the COM layer (QBXMLRP2) and mean the request never reached the parser at all. - Can a translation layer make Desktop errors look like QBO errors? Mostly. Shape and transport map cleanly; error codes don't map one-to-one, so a good layer preserves the original alongside the translated fault.
Two error systems, stacked
If you've only ever written against the QuickBooks Online API, error handling is boring in a good way. You get an HTTP status code that means what it says, and a JSON body:
{
"Fault": {
"Error": [{
"Message": "Invalid Reference Id",
"Detail": "Invalid Reference Id : Customers element id 4213 not found",
"code": "610",
"element": "CustomerRef"
}],
"type": "ValidationFault"
},
"time": "2024-03-11T09:14:22.101-07:00"
}
You can branch on type, log code, surface Detail to a user, and move on.
QuickBooks Desktop has no such thing. What it has is two unrelated error systems stacked on top of each other, neither of which was designed for a REST client.
Layer one is COM. The request processor — QBXMLRP2.RequestProcessor — is a COM object running on the same Windows machine as QuickBooks. Everything that goes wrong before your XML gets parsed shows up as an HRESULT: a 32-bit integer, conventionally written in hex, with the high bit set when it's a failure. 0x80040408. 0x80040401. 0x80040416. These aren't about your data. They're about the machine: QuickBooks isn't running, the company file is open in single-user mode, the user closed the file, your app's certificate was revoked in the Integrated Applications list.
Layer two is qbXML. If the request processor accepts your XML, QuickBooks parses it and returns a qbXML response. Every response node carries three attributes:
<InvoiceAddRs statusCode="3140" statusSeverity="Error"
statusMessage="There is an invalid reference to QuickBooks Customer "ExampleCo" in the Invoice. QuickBooks error message: Invalid argument. The specified record does not exist in the list." />
Note what's not there: an HTTP status. The transport succeeded. The COM call succeeded. As far as any HTTP-shaped client is concerned, this is a 200. The failure is an attribute on a node three levels deep in a document you have to parse to find out anything went wrong.
That's the first structural problem. The second is that qbXML supports batched requests — one message, many request nodes — and each response node gets its own statusCode. A single "request" can be half successful. There is no document-level status to check.
Why the codes are cryptic
qbXML status codes are three- and four-digit integers grouped loosely by area, and the grouping is more historical than logical. A rough map of the ones you'll actually hit:
| Code | What it actually means |
| --- | --- |
| 0 | Success. |
| 1 | Query returned no results. Not an error — severity is Info. |
| 500 | Unsupported qbXML version for this request. |
| 1000 | Internal QuickBooks error, usually file-level. |
| 3000 | Object specified in the request was not found. |
| 3070 | String too long for the field. |
| 3100 | Name already exists in the list. |
| 3120 | Object specified in the request cannot be found (list ID form). |
| 3140 | Invalid reference to a list element. |
| 3170 | Error when saving/modifying the object. Catch-all. |
| 3175 | The object is in use / cannot be modified as requested. |
| 3180 | Error saving the transaction — often a linked-txn or A/R account problem. |
| 3200 | EditSequence is out of date — someone else changed the record. |
| 3210 | Insufficient permission for the logged-in QuickBooks user. |
Three things make these hard to work with in practice.
One: the code doesn't tell you the field. QBO gives you element: "CustomerRef". qbXML gives you 3140 and a statusMessage that names the field in prose, in English, in a string whose exact wording varies by QuickBooks version and year. If you want structured field attribution, you have to parse the message — which means you're regexing against a string Intuit never promised to keep stable.
Two: the same code covers many causes. 3140 fires for a missing customer, a missing item, a missing account, a missing class, a missing sales-tax code — any reference that doesn't resolve. 3170 is worse: it's the general "modify failed" bucket, and the actual reason lives entirely in the message text. 3180 covers everything from "this credit is already fully applied" to "you can't post to that account type."
Three: the message text sometimes contains a second error from a third system. Look at the 3140 example above. QuickBooks error message: Invalid argument. The specified record does not exist in the list. That's the internal QuickBooks engine talking, wrapped in the SDK's message, wrapped in an XML attribute. Three layers of error reporting, one string.
The HRESULT side
HRESULTs are a different animal. The ones you'll meet, and what they usually mean:
| HRESULT | Meaning |
| --- | --- |
| 0x80040400 | qbXML parse failure — malformed request document. |
| 0x80040401 | Could not start QuickBooks. |
| 0x80040402 | Unexpected error in the request processor. |
| 0x80040408 | QuickBooks is not running, or was closed while your session was open. |
| 0x8004040A | QuickBooks is busy — a modal dialog is open and blocking automation. |
| 0x80040410 | The company file is open in a mode incompatible with your request. |
| 0x80040414 | QuickBooks has a dialog open and cannot process the request. |
| 0x80040416 | The application's access certificate was revoked by the QuickBooks admin. |
Notice the shape of that list. Almost none of these are about your code being wrong. They're about state on a Windows box — who's logged in, what file is open, whether a "Do you want to save this?" dialog is sitting on the desktop waiting for a human. That's the fundamental difference between Desktop and Online: Desktop errors are frequently environmental, and environmental errors need a different retry policy than validation errors.
What a translation layer has to do
The goal is to hand a caller who already speaks QBO an error object they can branch on without learning any of the above. Four jobs:
1. Pick the right HTTP status
qbXML gives you nothing to work with here, so the mapping has to be inferred from the code class:
- Validation and reference failures (
3000,3120,3140,3070) →400 - Permission failures (
3210, and the certificate HRESULTs) →403 - Not-found on a
GET-shaped read →404 EditSequenceconflicts (3200) →409- Duplicate name (
3100) →409or400, depending on whether the caller can reconcile - Environmental HRESULTs (
0x80040408,0x8004040A,0x80040414) →503, withRetry-Afterwhere it makes sense - Internal failures (
1000,0x80040402) →500
The 503 bucket is the important one. "QuickBooks is not running" is not a client error, and a caller who treats it as one will spend a lot of time debugging correct requests.
2. Preserve the original
Never throw the source away. A translated fault that hides statusCode="3140" is a fault you can't support. The original code, severity, and raw message belong in the response body — the point of translation is to give the caller a stable surface, not to erase the underlying detail. When a customer opens a ticket, the first thing you'll want is the untranslated code.
3. Attribute the field where you can
This is where honest engineering matters. You can extract the offending field from the message text for the common cases, and you should — but you should also be clear with yourself about the confidence level. A layer that always claims field attribution will lie occasionally. Better to emit element when the parse is high-confidence and omit it otherwise than to guess.
4. Say what to do about it
A code and a message tell you what happened. They don't tell a developer at 4pm on a Friday what to change. A cause and a resolution field cost nothing to carry and save a support round-trip.
Put together, a translated fault looks like a QBO fault with extra room:
{
"Fault": {
"Error": [{
"code": "610",
"Message": "Invalid Reference Id",
"Detail": "Customer reference 'ExampleCo' was not found in the company file.",
"element": "CustomerRef",
"cause": "The CustomerRef.value you sent does not match a ListID or Name in this file.",
"resolution": "Query /v3/company/{realmId}/query?query=select * from Customer where DisplayName = 'ExampleCo' to get the current Id, or create the customer first.",
"docs": "https://docs.tenkeybridge.com/compatibility/",
"source": {
"layer": "qbxml",
"statusCode": "3140",
"statusSeverity": "Error",
"statusMessage": "There is an invalid reference to QuickBooks Customer \"ExampleCo\" in the Invoice..."
}
}],
"type": "ValidationFault"
}
}
Your existing QBO error handler reads Fault.type and code and does what it always did. Your support engineer reads source. Nobody has to learn qbXML. We went deeper on the design tradeoffs behind that source block in an earlier post on making Desktop errors legible.
Where the mapping is genuinely lossy
Two honest caveats.
The code sets don't overlap. QBO has roughly a hundred documented fault codes; qbXML has its own numbering with different granularity. Some Desktop conditions have no QBO analogue at all — 3175 ("object in use") has no clean equivalent because Online doesn't have the same record-locking model. Some QBO codes have no Desktop trigger. Any mapping table is a set of judgment calls, and you should read the ones your provider made rather than assuming. Ours are in the compatibility matrix, including the cases where we map to a nearest-neighbor code and say so.
Batched writes complicate atomicity. If your integration sends what looks like one REST call and it fans out to multiple qbXML request nodes, partial success is possible at the qbXML layer. Whether the caller sees that as one fault or several is a design decision, and it changes how you write retry logic. Ask about it before you build.
Retries, specifically
Retry policy is where error mapping stops being cosmetic and starts costing money — duplicate invoices are expensive to unwind.
Safe to retry automatically:
0x80040408,0x8004040A,0x80040414— environmental. QuickBooks wasn't ready. Back off and retry; the request never reached the data layer.0x80040401— could not start QuickBooks. Retry with a longer backoff.
Never retry blindly:
3170,3180— catch-alls. The write may have partially succeeded before failing. Read back before retrying.- Any timeout. A timed-out write on a large company file may well have committed. This is the classic duplicate-transaction source.
Safe to retry only after fixing the request:
3140,3120,3000,3070,3100,3210— deterministic. Retrying the identical payload will produce the identical error.
Needs a read-modify-write cycle:
3200—EditSequenceis stale. Re-read the object, reapply your change, resubmit. Desktop's optimistic concurrency is stricter than QBO'sSyncToken, and code that shrugs off token mismatches on Online will start failing here.
The durable fix for the "did my write commit?" question is idempotency keys — send one with every write, have the layer deduplicate on it, and a retry after a timeout becomes safe by construction rather than by hope. If you're auditing an existing QBO integration for Desktop, the swap checklist covers this alongside the other behavioral differences worth testing before you ship.
FAQ
What does statusCode 3140 mean?
3140 means "there is an invalid reference to a QuickBooks list element" — you referenced a customer, item, account, class, or other list entry that doesn't exist in that company file. It's a validation error, so retrying the same payload will fail identically. Query the list to get the current ListID, or create the record first. Note that names in Desktop are hierarchical and colon-delimited (ExampleCo:Warehouse Build), so a sub-record referenced by its short name alone will also produce 3140.
Why do I get 0x80040408?
That HRESULT means QuickBooks Desktop is not running, or was closed after your session opened. It's environmental, not a problem with your request — the XML never reached the parser. This is common on unattended servers where an update prompt or a Windows restart closed the application. It's safely retryable with backoff, and if you're seeing it constantly, the real fix is at the hosting layer rather than in your code.
Can I retry a failed write safely?
It depends on where the failure happened. Environmental HRESULTs are safe to retry because the write never started. Timeouts and 3170/3180 catch-alls are not, because the transaction may have partially committed — read back first, or use an idempotency key so the layer can deduplicate for you.
Why does my request return HTTP 200 with an error in it?
That's raw qbXML behavior: the HTTP layer only reports whether the request processor was reachable, not whether QuickBooks accepted the data. A REST-shaped layer in front of Desktop has to inspect every response node's statusCode and synthesize an appropriate HTTP status — which is exactly the translation work described above.
Does statusSeverity="Warning" mean the write failed?
No. Warning means the operation completed but QuickBooks wants to tell you something — a rounding adjustment, an auto-created record, a field it silently ignored. Only Error means the operation was rejected. Treating warnings as failures is a common early bug, and treating them as pure success is how you miss the fact that a field you sent was dropped.
Do these codes change between QuickBooks versions?
The numeric codes are stable; the message text is not. Wording shifts between QuickBooks versions and year editions, so any logic that regexes statusMessage for field names or causes needs testing against every version you support — one more reason to branch on structured fields rather than strings. If you want to see the translated faults concretely, the sandbox will happily return them — send a bad CustomerRef and read what comes back.