Error handling
Integrations with QuickBooks Desktop fail in ordinary ways: the customer’s computer is off, QuickBooks has a dialog open, a name already exists. Every error from this API tells you which kind of failure it is, whether retrying can help, whether a write reached QuickBooks, and what to tell your customer. The full list of codes is in the error code reference.
The error object
Section titled “The error object”Every error response has this body, with the HTTP status in httpStatusCode as well:
{ "error": { "type": "INTEGRATION_ERROR", "code": "QBD_DUPLICATE_NAME", "message": "QuickBooks returned status 3100: The name \"Northwind Traders\" of the list element is already in use.", "userFacingMessage": "A record with this name already exists in QuickBooks Desktop.", "httpStatusCode": 409, "integrationCode": "3100", "requestId": "req_01j9x4m6v4c8k2t7q0r5s3w1zb", "cause": "QuickBooks names must be unique within a list, and across customers, vendors, employees and other names.", "fixes": [{ "actor": "developer", "action": "Use a different name, or look up and reuse the existing object." }], "docsUrl": "https://www.desktopaccountingapi.com/docs/errors/#qbd_duplicate_name", "retryable": false, "outcome": "not_applied", "param": "name", "details": { "requestId": "req_01j9x4m6v4c8k2t7q0r5s3w1zb", "qbxmlStatusCode": 3100, "qbxmlStatusMessage": "The name \"Northwind Traders\" of the list element is already in use." } }}| Field | Meaning |
|---|---|
type |
One of nine categories below. Route on it. |
code |
A stable code from the catalog. A code’s meaning never changes. |
message |
For you: specific, may contain IDs and field paths, never secrets. Log it. |
userFacingMessage |
For your customer: plain language, always present. Authentication, billing and internal problems are masked to a neutral sentence. |
httpStatusCode |
The HTTP status of the response. |
integrationCode |
The native code when QuickBooks or the Web Connector produced one: a qbXML status code ("3100"), a COM HRESULT ("0x80040414") or a Web Connector code ("QBWC1039"). |
requestId |
The request ID (req_...), also in the Daapi-Request-Id header. |
cause, fixes |
Why it happened, and what to do. Each fix names who acts: developer, end_user or support. |
docsUrl |
The section of the error reference for this code. |
retryable |
Whether repeating the same request, unchanged, can succeed. Also sent as the Daapi-Should-Retry header. The error reference gives each code a fuller retry rule. |
outcome |
Whether a write changed the company file: applied, not_applied, pending, unknown, or not_applicable for reads. |
param |
The request field at fault, as a path such as lines[2].amount, when known. |
details |
Extra data for some codes, such as pagesServed on CURSOR_EXPIRED, minimumQbxmlVersion on version errors, or a ranked diagnosis on timeouts and QBD_QUICKBOOKS_NOT_RESPONDING. The error reference lists the keys per code. |
Error types
Section titled “Error types”| Type | What happened | How to handle it |
|---|---|---|
INVALID_REQUEST_ERROR |
Your request is malformed or breaks a rule. Nothing reached QuickBooks. | Fix the code. Do not retry unchanged. |
AUTHENTICATION_ERROR |
Missing, invalid or revoked key. | Check configuration and alert yourself. |
PERMISSION_ERROR |
Valid key, action not allowed (wrong project, disabled connection). | Check the project and connection. |
BILLING_ERROR |
Production data requests are blocked by billing. | Fix billing in the dashboard. See Billing. |
RATE_LIMIT_ERROR |
Too many requests. | Wait for Retry-After and retry. See Rate limits. |
INTEGRATION_CONNECTION_ERROR |
Something on the customer’s computer: offline, QuickBooks closed or busy, a dialog open, the wrong file. | Show userFacingMessage, retry later if retryable. Log as a warning, not an incident. |
INTEGRATION_ERROR |
QuickBooks received the request and rejected it, for example a duplicate name or stale revision. | Usually a data problem to fix in code. integrationCode holds the qbXML status. |
OUTCOME_UNKNOWN_ERROR |
A write was sent and its result could not be confirmed. | Never resend it. See uncertain writes. |
INTERNAL_ERROR |
A problem on our side. | Retry reads. For writes, check outcome first. |
New types and codes can appear. Handle unknown values as a generic failure and keep userFacingMessage as the message.
Deciding whether to retry
Section titled “Deciding whether to retry”retryable (and the Daapi-Should-Retry header) answers one question: can the identical request succeed later without anyone changing it? Use it together with outcome:
retryable |
outcome |
Do this |
|---|---|---|
true |
not_applied |
Retry with backoff, honoring Retry-After. Reuse the same Idempotency-Key for writes. |
false |
not_applied |
Do not retry. Fix the request, or ask the customer to fix their setup. |
| any | pending |
Do not resend. The request is still running; read it with GET /v1/requests/{id}?waitSeconds=60. |
| any | unknown |
Do not resend. Verify in QuickBooks first. |
| any | applied |
The change happened. Do not resend. |
The SDKs follow this table for you: they retry network failures, 429 and 5xx responses whose Daapi-Should-Retry is true, with exponential backoff from 0.5 to 8 seconds, up to two retries by default, and never when outcome is pending or unknown.
Typed errors in the SDKs
Section titled “Typed errors in the SDKs”Each SDK raises one error class per type, with every field available as a property.
import { DesktopAccountingApi, ApiError, IntegrationConnectionError, OutcomeUnknownError,} from "@desktopaccountingapi/quickbooks-desktop";
const qb = new DesktopAccountingApi().forEndUser("eu_01j9...");
try { await qb.qbd.customers.create({ name: "Northwind Traders" });} catch (err) { if (err instanceof IntegrationConnectionError) { console.warn(err.code, err.requestId); // show err.userFacingMessage to your customer } else if (err instanceof OutcomeUnknownError) { // do not retry: look the customer up by name or externalId first } else if (err instanceof ApiError && err.code === "QBD_DUPLICATE_NAME") { // reuse the existing customer } else { throw err; }}from desktopaccountingapi import ( APIError, DesktopAccountingApi, IntegrationConnectionError, OutcomeUnknownError,)
qb = DesktopAccountingApi().for_end_user("eu_01j9...")
try: qb.qbd.customers.create(name="Northwind Traders")except IntegrationConnectionError as err: print(err.code, err.request_id) # show err.user_facing_message to your customerexcept OutcomeUnknownError: pass # do not retry: look the customer up by name or external_id firstexcept APIError as err: if err.code != "QBD_DUPLICATE_NAME": raisetry{ await qb.Qbd.Customers.CreateAsync(new CustomerCreateInput { Name = "Northwind Traders" });}catch (IntegrationConnectionException ex){ logger.LogWarning("{Code} {RequestId}", ex.Code, ex.RequestId); // show ex.UserFacingMessage to your customer}catch (OutcomeUnknownException){ // do not retry: look the customer up by name or ExternalId first}catch (ApiException ex) when (ex.Code == "QBD_DUPLICATE_NAME"){ // reuse the existing customer}try { qb.qbd().customers().create(new CustomerCreateInput("Northwind Traders"));} catch (IntegrationConnectionException e) { log.warn("{} {}", e.getCode(), e.getRequestId()); // show e.getUserFacingMessage() to your customer} catch (OutcomeUnknownException e) { // do not retry: look the customer up by name or externalId first} catch (ApiException e) { if (!"QBD_DUPLICATE_NAME".equals(e.getCode())) throw e;}| Type | TypeScript | Python | C# | Java |
|---|---|---|---|---|
| Base class | ApiError |
APIError |
ApiException |
ApiException |
INVALID_REQUEST_ERROR |
InvalidRequestError |
InvalidRequestError |
InvalidRequestException |
InvalidRequestException |
AUTHENTICATION_ERROR |
AuthenticationError |
AuthenticationError |
AuthenticationException |
AuthenticationException |
PERMISSION_ERROR |
PermissionError |
PermissionDeniedError |
PermissionException |
PermissionException |
BILLING_ERROR |
BillingError |
BillingError |
BillingException |
BillingException |
RATE_LIMIT_ERROR |
RateLimitError |
RateLimitError |
RateLimitException |
RateLimitException |
INTEGRATION_CONNECTION_ERROR |
IntegrationConnectionError |
IntegrationConnectionError |
IntegrationConnectionException |
IntegrationConnectionException |
INTEGRATION_ERROR |
IntegrationError |
IntegrationError |
IntegrationException |
IntegrationException |
OUTCOME_UNKNOWN_ERROR |
OutcomeUnknownError |
OutcomeUnknownError |
OutcomeUnknownException |
OutcomeUnknownException |
INTERNAL_ERROR |
InternalError |
InternalError |
InternalException |
InternalException |
CURSOR_EXPIRED |
CursorExpiredError |
CursorExpiredError |
CursorExpiredException |
CursorExpiredException |
| No response at all | ApiConnectionError |
APIConnectionError |
ApiConnectionException |
ApiConnectionException |
A pattern for production
Section titled “A pattern for production”- Branch on
typefirst, then on the fewcodevalues you handle specially. Treat everything else in a type the same way. - Show
userFacingMessage, notmessage, to customers. It is safe by design, and for connection errors it tells them exactly what to do on their computer. Link the help center next to it. - Alert on your own failures, not your customers’.
AUTHENTICATION_ERROR,INVALID_REQUEST_ERROR,INTERNAL_ERRORand a rise inINTEGRATION_ERRORdeserve attention.INTEGRATION_CONNECTION_ERRORis usually a customer’s PC being off. - Store
requestIdwith failures. The dashboard request log opens it directly, with the timeline and native QuickBooks status, and support can find it immediately. - Prefer user-started syncs. When a sync runs because a person clicked a button, a connection error becomes a message they can act on. For scheduled syncs, record the last error per customer and show it in your UI.
QuickBooks status codes
Section titled “QuickBooks status codes”When QuickBooks rejects a request, integrationCode holds its qbXML status code and details.qbxmlStatusMessage holds QuickBooks’ own text. Common ones map to dedicated codes:
| qbXML status | Code |
|---|---|
| 3100 | QBD_DUPLICATE_NAME |
| 3120, 3140 | QBD_OBJECT_NOT_FOUND for the ID in the path, QBD_REFERENCE_NOT_FOUND for a referenced ID (with param) |
| 3175, 3176 | QBD_OBJECT_IN_USE (someone has the record open in QuickBooks; for a deletion, other records may still use it) |
| 3200 | QBD_REVISION_NUMBER_STALE |
| 3250 | QBD_FEATURE_NOT_ENABLED |
| 3260 | QBD_INSUFFICIENT_PERMISSION (the QuickBooks user’s role) |
| 3261 | QBD_INSUFFICIENT_PERMISSION (the app may not access personal data, for example payroll reports) |
| Any other error status | QBD_REQUEST_ERROR |
Connection-level failures carry the COM HRESULT that QuickBooks returned, such as 0x80040414 for an open dialog or 0x8004040A for the wrong company file. The error reference lists every mapped code.