- A read that was sent, interrupted (for example when QuickBooks closed unexpectedly, HRESULT 0x80040402) and queued for its automatic retry now ends with
QBD_READ_INTERRUPTED(502,details.reason,details.retry,details.firstSentAt) when the retry is not sent in time. It answered504 REQUEST_TIMEOUT_NOT_SENTwithsentAt: null. A request'ssentAtis no longer reset to null by the retry. - New diagnosis cause
quickbooks_closed_unexpectedly: QuickBooks returned an error instead of a result (0x80040402 when it crashed) and the Web Connector, still running with a stuck update lock, has not checked in since. It ranks aboveweb_connector_closed.diagnosis.connector.lastSeenAtnow counts the calls of a session that ended, not only the last check-in. - Breaking:
qbd.unitOfMeasureSets.deleteis removed. QuickBooks answered status 1000 and Intuit documents that a unit of measure set cannot be deleted;DELETE /v1/quickbooks-desktop/unit-of-measure-sets/{id}now answers422 QBD_OPERATION_UNSUPPORTEDwith alternatives (make the set inactive in QuickBooks Desktop) and sends nothing. - Breaking:
qbd.fixedAssetItems.createrequiresassetAccountId. QuickBooks rejects a fixed asset item without an asset account (status 3180), although the qbXML schema marks it optional. - Creating or updating a public custom field definition (
ownerId"0") accepts onlycustomer,vendor,employeeanditeminassignToObjectsandremoveFromObjects; other values answer400 INVALID_PARAMETERbefore anything is sent. QuickBooks refused them with status 3351. - A QuickBooks status 3231 for a request that was alone in its message set carries
details.onlyRequestInMessageSet: trueand says no earlier request caused it. Lead requests and class merges have documented incompatibilities (status 3231 and 3500 on QuickBooks Enterprise 24). - Breaking: a list-object delete (
qbd.<list>.delete, QuickBooksListDel) no longer always answersdeleted: true. QuickBooks Desktop can accept the delete and keep the object, inactive and readable by its ID (Enterprise 24 did this in every test). The API reads the object back in the same request:deletedis true only when QuickBooks no longer returns it; otherwisedeletedis false,deactivatedis true when it is now inactive, andisActiveholds its state. The result types gaindeactivatedandisActive. QBD_REQUEST_ERRORon a read (for example a payroll report QuickBooks refused with status 3262) has the end-user text "QuickBooks Desktop could not provide this information." It said "did not accept this change".errors.jsondocumentsreadUserFacingMessage.- Breaking: custom field definition update and delete take the definition name as the path parameter
definitionName(it wasname). - Breaking: one fallback type per union. An item or entity of a kind this SDK does not model, whether QuickBooks returned a kind the API does not model (
objectTypeqbd_unrecognized_item) or the API added a kind after this SDK version, is anUnrecognizedItem/UnrecognizedEntitywith the shared fields plusqbxmlElementandfields; itsobjectTypeis a plain string.UnknownItemandUnknownEntityare gone. - Breaking: identical enums share one type, used on requests and responses alike, for example
AssignToObject(alsoCustomFieldDefinition.assignToObjects),LeadStatus,ToDoType,ToDoPriority,ClearedStatus,BillingStatus,CustomFieldOperation,TransactionKindandOutcome. The per-operation names (LeadCreateInputStatus,CustomFieldDefinitionCreateInputAssignToObject,RequestListParamsStatu, ...) are gone. - Breaking: type renames:
LeadContactsCreateInput/LeadContactsUpdateInput/LeadContactsareLeadContactCreateInput/LeadContactUpdateInput/LeadContact;DeletedCustomFieldDefinitionisCustomFieldDefinitionDeleted; the result oftransactions.updateClearedStatusisTransactionClearedStatus, whoseclearedStatusis aClearedStatus. - Breaking: to-do list parameters lose the name filters (a to-do has no name), and
payrollWageItems.listno longer takescursor(the list is never paginated; any cursor was refused). IntegrationConnection(an end user'sintegrationConnections) carriesendUserId,disabledandsetupCompletedAt, likeEndUserConnection, which has everyIntegrationConnectionfield plusinstallsandcompanyFileMismatches.- The
createdAtandupdatedAtof every*Voidedresult are date-times. CursorExpired'sreasonis a typed set (idle_timeout,session_ended,quickbooks_restarted,evicted, or a value added later). The cursor-expired error stays a subclass of the invalid-request error, because the API classifiesCURSOR_EXPIREDasINVALID_REQUEST_ERROR; catch it first when you handle the two differently.- Breaking: enums are typed. Each enum (except the
ErrorCodeandErrorTypeconstants) is a final class with a constant per known value (LeadStatus.HOT),of(String)for any other value,value(),isKnown()andknown(), which returns a JavaenumwithUNRECOGNIZEDfor an exhaustiveswitch. Fields, setters and getters use it instead ofString. The SDK targets Java 11, so unions are plain interfaces, not sealed ones. - Breaking: deletes and merges take the confirmation in the constructor:
new CustomerDeleteParams(Confirm.YES),new ListMergeInput(revisionNumber, mergeIntoId, mergeIntoRevisionNumber, Confirm.YES). There is noconfirm(...)setter. CustomFieldValueInputandTransactionCustomFieldValueInputimplementCustomFieldValueInputFields;ListMergeInputandCustomerMergeInputimplementListMergeInputFields.- Breaking:
ApiException.status()returnsint(the response status, else the error'shttpStatusCode, else 0) andretryable()returnsboolean(false when the body was not JSON).CursorExpiredException.reason()returns aCursorExpiredReason. - Breaking: a list-object delete's
deletedanddeactivatedcan be null: QuickBooks accepted the delete but reading the record back failed. The request then succeeds withoutcomeapplied and a newQBD_DELETE_NOT_VERIFIEDwarning; do not send the delete again. A failed write the API verified as applied keepsoutcomeapplied and is not retryable. - List-object delete results gain
retainedBecause:multi_user_modewhen QuickBooks kept the record because the company file was open in multi-user mode (it deletes in single-user mode). - New error code
QBD_REQUIRES_SINGLE_USER_MODE(409, not retryable, outcomenot_applied): merges and custom field definition deletes are refused before sending while the company file is open in multi-user mode, and custom field values QuickBooks refuses there (status 3180 or 3120) map to it.integrationConnections[].companyFile.fileMode(single_user/multi_user) shows the mode of the last session. - A lead request QuickBooks does not process (status 3231, even a plain lead list) answers
422 QBD_OPERATION_UNSUPPORTEDwith alternatives instead ofQBD_REQUEST_ERROR. qbd.vehicleMileage.deletereads the trip first: an unknown ID answers404 QBD_OBJECT_NOT_FOUND; an existing trip answers422 QBD_OPERATION_UNSUPPORTED(QuickBooks refuses the delete with status 3160 even on a fresh company file) without sending the delete.- The
quickbooks_closed_unexpectedlydiagnosis needs QuickBooks' "Unexpected error" (HRESULT 0x80040402) in a session the Web Connector never closed, and silence since its last contact; its steps first have the end user check that QuickBooks closed and no other application is updating. - Batches (
qbd.batch) follow the same rules: a list-delete member reads the record back (deleted,deactivated,retainedBecause, and aQBD_DELETE_NOT_VERIFIEDmember warning when the read-back failed), a batch holding a merge or custom field definition delete is refused with409 QBD_REQUIRES_SINGLE_USER_MODEin multi-user mode (details.indexnames the member), a lead member QuickBooks does not process isQBD_OPERATION_UNSUPPORTED, a vehicle mileage delete member is refused with422 QBD_OPERATION_UNSUPPORTED, and batch delete and merge members requireconfirmlike the typed calls. - Breaking: a batch member for custom field definition update or delete takes
pathParams.definitionName(waspathParams.name), as the typed calls do. - Breaking: the batch's
onErroris the shared enum classBatchOnErroron the request and the result (BatchInput.onError(BatchOnError.CONTINUE_ON_ERROR), no longer aString); a batch member'soutcomeisOutcome, like the error's. - A custom field definition delete reads the owner's definitions back in the same request:
CustomFieldDefinitionDeletedgainsdeleted(true only when the definition is gone, false when QuickBooks kept it, null when the read-back failed with aQBD_DELETE_NOT_VERIFIEDwarning) andretainedBecause(multi_user_modeorunknown), on the typed call and as a batch member. QBD_REQUIRES_SINGLE_USER_MODEnow needs QuickBooks' own evidence: a custom field value refused with another 3180/3120 message keeps its code (for example not found, orQBD_TRANSIENT_ERRORfor a record modified by another user); a generic data-extension refusal in multi-user mode keeps its code withdetails.possibleCause: multi_user_mode.- A batch member QuickBooks refused is
not_appliedin its result and its error, whatever other members did; a lead member undercontinueOnErrorafter another member failed isQBD_OPERATION_UNSUPPORTED, notnot_processed; the vehicle mileage refusal carriesdetails.index. - New error code
QBD_REAUTHORIZATION_REQUIRED(ErrorCode.QBD_REAUTHORIZATION_REQUIRED):503, not retryable. QuickBooks answered the Web Connector with 0x80040435 and asks its Admin to reauthorize the application in Edit > Preferences > Integrated Applications; nothing was sent. It answeredQBD_CONNECTION_ERROR. New diagnosis causequickbooks_reauthorization_required. - A company file QuickBooks cannot reach at its stored location (0x80040408 for a path on a drive letter other than C:, usually a disconnected mapped network drive, or for the same path twice in a row) answers
QBD_COMPANY_FILE_NOT_FOUNDwithdetails.reasonpath_unreachable,details.pathKindanddetails.refusals, and the new diagnosis causecompany_file_not_foundranks first. It answeredQBD_WRONG_COMPANY_FILE_OPEN("a different company file is open"). - A QuickBooks status 3170 whose message says the record is in use, locked or being edited answers
409 QBD_OBJECT_IN_USEwith therecord_in_usediagnosis (not retryable for a deletion). It answered422 QBD_REQUEST_ERROR. QuickBooks does not lock every open record, so the documentation no longer promises that an open window blocks a change. - A payment item or job type name that is already in use answers
409 QBD_DUPLICATE_NAMEwithparamname(QuickBooks status 3180). It answered422 QBD_REQUEST_ERROR. qbd.customFieldDefinitions.listwithout filters returns the public definitions (ownerId"0"); it returned none.ownerIdstogether withassignToObjectslists the definitions of those owners assigned to those record types, private ones included; the combination was refused.- Vehicle mileage
createdAtandupdatedAtare date-times: midnight UTC when QuickBooks reports only a date. They were plain dates, sovehicleMileagecalls threw a parse error in this SDK. - A single request ends its Web Connector session as soon as QuickBooks answers it, so QuickBooks no longer counts the application as using the company file for 2 seconds afterwards; only a request sent within 30 seconds of another keeps the session open for the next one.
- New
client.qbd().batch(): up to 25 typed QuickBooks Desktop operations in one Web Connector round trip. Each member is the member class of its operation (new BatchCustomerCreateInput().body(...), which setsoperationIditself) and each result is an instance of its operation's result class (BatchCustomerCreateResult, whosedata()is aCustomer), orUnknownBatchResultfor an operation added later. One idempotency key covers the batch; a lost response throwsOutcomeUnknownExceptionwithdetails().get("members")and is never retried. - A batch whose response was lost and whose create members the API later found in QuickBooks by their
externalIdreturns each such member withdata()null and the record's QuickBooks ID inrecoveredObjectId()(on every batch result, nullable), onclient.requests().retrieve()and on an idempotent replay. - New error code
QBD_REQUEST_NOT_PROCESSED(ErrorCode.QBD_REQUEST_NOT_PROCESSED):409, not retryable. QuickBooks skipped a batch member because an earlier member failed understopOnError(status 3231). - Breaking (types only): the credit card request info of receive payments is the class
QbdRequest. It was read as the request resource classRequestby mistake, so its accessors returned null. - Change feed subscriptions:
client.changeFeedSubscriptions().create(),list(),retrieve(),update()anddelete()(/v1/change-feed-subscriptions). A subscription polls every connected end user of the project for changed and deleted records of the listed resources and sendsdata.changedwebhooks with their IDs. New webhook event typeWebhookEventType.DATA_CHANGED(data.changed). New error codeCHANGE_FEED_SUBSCRIPTION_LIMIT_REACHED(422, not retryable).
- Breaking: the list parameters type of
qbd.classes.listis renamed fromClasseListParamstoClassListParams(the generator dropped only the final "s" of "classes"). Rename the type in your code; nothing else changes. No other type name was affected. client.webhookEndpoints().rotateSecret()works: the API now honors the Idempotency-Key the client always sends (a retry returns the same new secret without rotating twice) and answers an unknown endpoint ID with404 RESOURCE_MISSING. It answered400 INVALID_PARAMETER.- New error code
QBD_SALES_TAX_THROUGH_DATE_TOO_EARLY(ErrorCode.QBD_SALES_TAX_THROUGH_DATE_TOO_EARLY):422, not retryable. QuickBooks refused a sales tax payment check and asked for a later sales tax 'through' date (status 3140); nothing changed. If the end user records the sales tax in QuickBooks instead, read and link that check rather than creating another. It answeredQBD_REFERENCE_NOT_FOUNDonvendorId. - A sales representative initial that is already in use answers
409 QBD_DUPLICATE_NAMEwithparaminitial(QuickBooks status 3180). It answered422 QBD_REQUEST_ERROR. QBD_OBJECT_IN_USEisretryableonly when the record is open or locked in QuickBooks. A deletion or void refused because other records still use the object isretryable: false, so the client no longer treats it as worth repeating.- Status 3390 (too many open QuickBooks list iterators) is
QBD_TRANSIENT_ERRORwithdetails.reasonmax_iterators. The API now stops the QuickBooks iterator of a cursor you stop reading (after about 10 seconds idle, or when a fourth cursor list starts for the same end user), so breaking out of a list loop early no longer uses up QuickBooks' iterator limit. - Report validation:
missing_checksrequiresaccountIdsoraccountFullNames, the job detail reports requireentityIdsorentityFullNames, andphysical_inventory_worksheetrejectsreportDateMacro, each with400 INVALID_PARAMETERnaming the parameter. Unit of measure set create rejectsisActive: false, which QuickBooks ignores. - New error code
QBD_TRANSIENT_ERROR(ErrorCode.QBD_TRANSIENT_ERROR):503, retryable, outcomenot_applied. QuickBooks was briefly busy (status 3390, or 3180 when a list was modified by another user) and changed nothing. The client retries it automatically with the same idempotency key, which now runs the write again instead of replaying the error. These answeredQBD_REQUEST_ERROR(422, not retryable).
Listed after the release (2026-10-12): the entry above named the fixes but not the operations 0.6.0 added. 155 new operations, by operation ID (method names follow each SDK's conventions):
qbd.accounts:delete,merge,setCustomFieldqbd.billCheckPayments:setCustomFieldqbd.billCreditCardPayments:setCustomFieldqbd.billingRates:create,delete,list,retrieveqbd.bills:setCustomFieldqbd.buildAssemblies:setCustomFieldqbd.checks:setCustomFieldqbd.classes:delete,mergeqbd.creditCardCharges:setCustomFieldqbd.creditCardCredits:setCustomFieldqbd.creditCardRefunds:setCustomFieldqbd.creditMemos:setCustomFieldqbd.currencies:deleteqbd.customFieldDefinitions:create,delete,list,updateqbd.customerMessages:create,delete,list,retrieveqbd.customerTypes:deleteqbd.customers:delete,merge,setCustomFieldqbd.dateDrivenTerms:deleteqbd.deposits:setCustomFieldqbd.discountItems:delete,merge,setCustomFieldqbd.employees:delete,setCustomFieldqbd.entities:list,retrieveqbd.estimates:setCustomFieldqbd.fixedAssetItems:create,delete,list,retrieve,setCustomField,updateqbd.inventoryAdjustments:setCustomFieldqbd.inventoryAssemblyItems:canBuild,delete,setCustomFieldqbd.inventoryItems:delete,merge,setCustomFieldqbd.inventorySites:deleteqbd.inventoryTransfers:create,delete,list,retrieve,updateqbd.invoices:setCustomFieldqbd.itemGroups:delete,merge,setCustomFieldqbd.itemReceipts:setCustomFieldqbd.items:list,retrieveqbd.jobTypes:create,delete,list,retrieveqbd.journalEntries:setCustomFieldqbd.leads:create,list,retrieve,updateqbd.nonInventoryItems:delete,merge,setCustomFieldqbd.otherChargeItems:delete,merge,setCustomFieldqbd.otherNames:delete,setCustomFieldqbd.paymentItems:create,delete,list,merge,retrieve,setCustomField,updateqbd.paymentMethods:deleteqbd.payrollNonWageItems:delete,list,retrieveqbd.payrollWageItems:delete,updateqbd.priceLevels:deleteqbd.purchaseOrders:setCustomFieldqbd.receivePayments:setCustomFieldqbd.salesOrders:setCustomFieldqbd.salesReceipts:setCustomFieldqbd.salesRepresentatives:deleteqbd.salesTaxCodes:deleteqbd.salesTaxGroupItems:delete,merge,setCustomFieldqbd.salesTaxItems:delete,merge,setCustomFieldqbd.salesTaxPaymentChecks:setCustomFieldqbd.serviceItems:delete,merge,setCustomFieldqbd.shippingMethods:deleteqbd.standardTerms:deleteqbd.statementCharges:create,delete,list,retrieve,setCustomField,update,voidqbd.subtotalItems:delete,merge,setCustomFieldqbd.toDos:create,delete,list,retrieve,updateqbd.transactions:updateClearedStatusqbd.unitOfMeasureSets:deleteqbd.vehicleMileage:create,delete,list,retrieveqbd.vehicles:create,delete,list,retrieve,updateqbd.vendorCredits:setCustomFieldqbd.vendorTypes:create,delete,list,retrieveqbd.vendors:delete,merge,setCustomFieldqbd.workersCompCodes:create,delete,list,retrieve,update
- New error code
ROUTE_NOT_FOUND(ErrorCode.ROUTE_NOT_FOUND): a404for a method and path the API has no endpoint for, such as the API root. These answeredRESOURCE_MISSING, whose cause describes a missing ID.
- Released in lockstep with the other Desktop Accounting API packages; no entries for this package.
- Released in lockstep with the other Desktop Accounting API packages; no entries for this package.
ApiResponse.requestId()after a long-polled call is the ID of the request that produced the result (the 504'sdetails.requestId), whichclient.requests().retrieve()finds. It was the last poll's ID, which answers404. The poll's own ID stays inheaders().get("Daapi-Request-Id").- The README says that required input fields are checked before the request is sent, not by the compiler: a field you never set, such as
fiscalYearonReportBudgetSummaryParams, throwsDaapiExceptionand nothing is sent. The constructor that takes the required fields is the compile-time-checked way to build an input. QBD_OBJECT_IN_USEerrors (QuickBooks status 3175 or 3176, a record open for editing or locked by another user) carrydetails.diagnosiswith the new diagnosis causerecord_in_use: close the edit window in QuickBooks, then retry. The cause'sdetails.lockedBynames the user when QuickBooks does. The request'sdiagnosishas the same cause.
- A
504 QBD_REQUEST_TIMEOUTis long-polled only when its HTTP status is 504 and it names adetails.requestId, the rule every SDK follows (reads with outcomenot_applicableand writes alike). Such a 504 is never retried. - Breaking: a model field named
valueis nowvalue()(andvalue(...)on inputs) instead ofvalueValue(), for exampleReportCell.value(),CustomField.value()andBarcodeCreateInput.value(...). The protectedInputObject.value(String)helper is renamedstored(String). - Breaking (types): Response prices, rates and percentages (for example
QbdInvoiceLine.rate,QbdSalesOrPurchaseDetail.price,ratePercent) carry the same decimal pattern as their inputs and as amounts, so they areBigDecimalinstead ofString. - The README documents resuming a list from a stored
nextCursor()(new InvoiceListParams().cursor(savedCursor)), now covered by the cross-language conformance suite, and the webhook verifier'sjava.time.Clock.
- Warning code
QBD_PERSONAL_DATA_WITHHELD: employee responses carry one warning perssn()that QuickBooks withheld because the integration may not read personal data (ssn()staysnull;Daapi-Warningscounts the warnings). personalDataAccess()on an end user's integration connections:allowed,deniedorunknown.- A failed passthrough's error
details().get("requests")lists the status code, severity and message of every qbXML message, so a message skipped bystopOnErroris visible.
- Breaking:
qbd().reports().budgetSummary(params)now requiresfiscalYearinReportBudgetSummaryParams(a required field, likereportType; the two-argument constructor sets both). The API always rejected a budget report without it (400 INVALID_PARAMETER,param: "fiscalYear"), so no working call changes behavior; code that omitted it still compiles and now throwsDaapiExceptionbefore sending instead of the API error. Set it withnew ReportBudgetSummaryParams(reportType, 2026)or.fiscalYear(2026). WebhookEventType.CONNECTION_COMPANY_FILE_REMARKED(connection.company_file_remarked): the marker that identifies a connection's company file was created, written back after the file lost it (for example a restored backup) or adopted from the file;data.reasonismarker_created,marker_restoredormarker_adopted.ApiException.getRequestId(), the same value asrequestId(), for code and tools that expect bean-style getters.ApiException.toString()is documented: it adds the HTTP status, the error code and the request ID to the message (...IntegrationException: 404 QBD_OBJECT_NOT_FOUND The QuickBooks object does not exist. (req_...)), as .NET'sToString()does;getMessage()stays the API message. Log the exception, not onlygetMessage(), to keep the request ID.- After
504 QBD_REQUEST_TIMEOUT, any failure while waiting for the request (a poll answered429,5xxor404, a network error or a timeout) throwsRequestPendingExceptionwithrequestId(),timeoutError()(the 504, also the cause),pollError()andidempotencyKey(). It never surfaces the poll's own retryable exception, which read as "safe to resend" and could duplicate a write.RequestHandle.awaitfollows the same rule. - Waiting for a pending request stays inside the call's deadline (
totalTimeout, elsetimeout): each poll, retry and backoff is cut off at the deadline. idempotencyKey()on every exception thrown for a write (generated or yours), onApiResponseand onRequestHandle.- A request that succeeded in QuickBooks but whose answer the API could not map (
request.error, for exampleQBD_RESPONSE_UNREADABLEwith outcomeapplied) throws that typed exception instead of failing to parse a null result. - Exceptions thrown by
RequestHandle.awaitandresult()carry the handle'sidempotencyKey(). - A poll answer that arrives after the deadline (the default transport times the headers, not the body) is not returned, even a settled one; the call throws
RequestPendingExceptionwith that snapshot. - The default transport bounds the whole exchange, headers and body, by the attempt timeout (
HttpClient.sendAsynccanceled at the timeout), so a trickling body no longer holds the calling thread past the deadline.
Generated from API contract sha256 b5774d24bc81. Documentation only; no API surface change.
updatedAtandrevisionNumberdescriptions say that QuickBooks changes them at most once per second: an incremental sync should overlapupdatedAfterand deduplicate byidandrevisionNumber.- The fixes for status 3261 (
QBD_INSUFFICIENT_PERMISSION) name the personal-data checkbox in QuickBooks and what to do when it is gray: send the end user a new setup link and choose "Enable payroll access". - Item sites document what QuickBooks returns without Advanced Inventory: an empty list, and
404 QBD_OBJECT_NOT_FOUNDfrom retrieve, rather than an error.
defaultHeader(...)/defaultHeaders(...)on the client builder, andtotalTimeout(...)on the builder andRequestOptions: a time budget for a whole call, including retries and the wait for a pending request.- A base URL ending in
/v1(Conductor's form) no longer produces/v1/v1/.... Pager<T>requests the next page only when the iteration needs it, so a loop that stops early sends no extra QuickBooks query. While iterating items, a page held for more than 2 seconds makes the SDK request the next page in the background;listAll()always reads ahead.- README: "Porting from Conductor".
Generated from API contract sha256 1cc3058cecb5, the same contract as 0.1.0. No API surface change.
- The README is rewritten: install with exact package coordinates, authentication, a quickstart, common workflows, errors, async requests and webhooks, versioning and support. Every code sample in it is compiled against the package before release, and the quickstart runs against a mock server.
First release, generated from API contract 1.0.0 (sha256 1cc3058cecb5, 275 operations).
DesktopAccountingApiClientwith builder,fromEnv()andforEndUser(...); local secret-key validation.- Typed models for every request and response:
BigDecimalmoney with preserved scale,LocalDate,OffsetDateTime, open enums as string constants, unknown fields kept inadditionalProperties(),toJson()on every model. - Input objects that distinguish unset fields from explicit
null. - Auto-pagination (
Pager<T>:Iterable,stream(),firstPage(),pages(),listAll()) with one page of read-ahead andCursorExpiredExceptionprogress fields. - Retries for network errors, 429 and retryable 5xx responses with backoff,
Retry-Afterand one idempotency key per call. - Long-polling after
504 QBD_REQUEST_TIMEOUTandRequestPendingException. - Async mode through
enqueue()returningRequestHandle<T>. - Typed exceptions per error type with every error field;
ErrorCodeandErrorTypeconstants. ...WithResponse(...)methods returning status and headers.- Passthrough as JSON or raw qbXML.
- Standard Webhooks verification (
Webhooks,WebhookVerifier). - No runtime dependencies; Java 11 or later.