- 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:
confirmon deletes and merges is a requiredLiteral[True]keyword argument. - Breaking: models used only as an operation's whole query or body (the ~76
*ListParams, and inputs such asCustomerCreateInput) are no longer generated or exported: every method takes keyword arguments, so they could not be passed anywhere. Models nested in requests (InvoiceLineCreateInput) stay. - New exported type
CursorExpiredReason. - 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. - 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, which the Java SDK could not parse. - 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 (BatchCustomerCreateInput(body=CustomerCreateInput(...)), which setsoperation_iditself), and each result is an instance of its operation's result class (BatchCustomerCreateResult, whosedatais aCustomer), orUnknownBatchResultfor an operation added later. One idempotency key covers the batch; a lost response raisesOutcomeUnknownErrorwithdetails["members"]and is never retried. - A batch whose response was lost and whose create members the API later found in QuickBooks by their
external_idreturns each such member withdataNoneand the record's QuickBooks ID inrecovered_object_id(on every batch result:str | None), 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 model
QbdRequest. It was parsed as the request resource modelRequestby mistake, so its fields read as None. - Change feed subscriptions:
client.change_feed_subscriptions.create(),.list(),.retrieve(),.update()and.delete()(/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 typeEventType.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.webhook_endpoints.rotate_secret()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.
- Releases publish to PyPI through trusted publishing (GitHub OIDC); no PyPI token is used.
- Released in lockstep with the other Desktop Accounting API packages; no entries for this package.
RawResponse.request_idandCursorPage.request_idafter a long-polled call are the ID of the request that produced the result (the 504'sdetails["requestId"]), whichclient.requests.retrieve()finds. They were the last poll's ID, which answers404. The poll's own ID stays inraw.headers["daapi-request-id"](sync and async clients).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 read that hits the server timeout (
504 QBD_REQUEST_TIMEOUTwith outcomenot_applicable) is long-polled like a write: the SDK waits onGET /v1/requests/{id}until the call's deadline and returns the result or raisesRequestPendingError, instead of raising the 504 at once (sync and async clients). The rule is the same in every SDK: HTTP 504, codeQBD_REQUEST_TIMEOUTand adetails.requestId, whatever the outcome. Such a 504 is never retried. end_users.passthrough_xml()collects a request that hits the server timeout the same way and returns its qbXML result text, instead of raising the 504 (sync and async clients).- An error that ends the wait for a timed-out write (for example
OutcomeUnknownErrorfrom a request that endedoutcome_unknown) carries the write'sidempotency_key; it wasNone. - Cursor lists accept
cursor=, so a list can resume from a storednext_cursor, for example across HTTP requests:client.qbd.invoices.list(cursor=saved_cursor, limit=100).first_page(). Iteration continues from that page. - 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 aredecimal.Decimalinstead ofstr. - The
now=option ofwebhooks.verifyandverify_signatureis documented as Unix seconds (liketime.time).
- Warning code
QBD_PERSONAL_DATA_WITHHELD: employee responses carry one warning perssnthat QuickBooks withheld because the integration may not read personal data (ssnstaysNone;Daapi-Warningscounts the warnings). personal_data_accesson an end user's integration connections:allowed,deniedorunknown.- A failed passthrough's error
details["requests"]lists the status code, severity and message of every qbXML message, so a message skipped bystopOnErroris visible.
- Breaking:
qbd.reports.budget_summary()now requires thefiscal_yearkeyword argument (sync and async clients). The API always rejected a budget report without it (400 INVALID_PARAMETER,param: "fiscalYear"), so no working call changes behavior; type checkers flag calls that omit it, and such calls raiseTypeError. Pass the fiscal year, for examplebudget_summary(report_type="profit_and_loss_budget_overview", fiscal_year=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["reason"]ismarker_created,marker_restoredormarker_adopted.- After
504 QBD_REQUEST_TIMEOUT, any failure while waiting for the request (a poll answered429,5xxor404, a network error or a timeout) raisesRequestPendingErrorwithrequest_id,timeout_error(the 504),poll_errorandidempotency_key. It never surfaces the poll's own retryable error, which read as "safe to resend" and could duplicate a write.RequestHandle.wait()follows the same rule (sync and async clients). - Waiting for a pending request stays inside the call's deadline (
total_timeout, elsetimeout): no poll, SDK retry or backoff starts after it. The async client cuts an in-flight poll off at the deadline; the sync client stops it between network operations (see below). idempotency_keyon every error raised for a write (generated or yours), on raw responses and on request handles.- A request that succeeded in QuickBooks but whose answer the API could not map (
request.error, for exampleQBD_RESPONSE_UNREADABLEwith outcomeapplied) raises that typed error instead of returningNone. - Errors raised by
RequestHandle.wait()andresult()(sync and async) carry the handle'sidempotency_key. - A poll answer that arrives after the deadline (httpx timeouts are per I/O step) is not returned, even a settled one; the call raises
RequestPendingErrorwith that snapshot. - The call's deadline also bounds the response body. The async client runs each attempt under
asyncio.wait_for(a hard bound, headers and body). The sync client enforces it between network operations, without threads: each attempt's connect, read, write and pool timeouts are the time left, the deadline is checked after headers and between body chunks, and the response is closed when it passes. The sync deadline is checked again after the body and its cleanup (chunked trailers, EOF) before a success is returned. One stalled sync read can overrun by at most its read budget, and trickling header bytes can extend a sync attempt. The SDK never starts its own attempt or retry after the deadline. The sync client cannot bound everything: retries inside an injected transport (for examplehttpx.HTTPTransport(retries=3)), synchronous DNS lookups, and your own auth, hook, transport or cleanup code can run longer; how httpx applies its connect, read, write and pool timeouts depends on the transport; and a transport error that arrives after the deadline surfaces asAPIConnectionErrorrather thanAPITimeoutError. Sync requests always go through yourhttpx.Client, so auth flows, event hooks, cookies, proxies andtrust_envbehave the same with a 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.
Easier porting from Conductor's conductor-py; see "Porting from Conductor" in the README.
conductor_end_user_id=is accepted on every QuickBooks Desktop method as an alias ofend_user_id=. Both with different values raiseDaapiError.- Conductor's error names:
ConductorError(alias ofDaapiError),APIStatusError,BadRequestError,NotFoundError,ConflictError,UnprocessableEntityErrorandInternalServerError. Every error from an HTTP response is also anAPIStatusErrorand, for those statuses, the matching status class, soexcept NotFoundError:works;APIError.status_codeis an alias ofstatus. - Client options
default_headersandtotal_timeout(also per call), and theDAAPI_LOGenvironment variable. A base URL ending in/v1no longer produces/v1/v1/.... - Pagination 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;
list_all()always reads ahead.
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 version 1.0.0 (contract sha256:1cc3058cecb5, 275 operations).
- Synchronous
DesktopAccountingApiand asyncioAsyncDesktopAccountingApiclients on httpx, for Python 3.9 to 3.14. - Every QuickBooks Desktop and platform operation as a typed method (
client.qbd.invoices.create(...),client.end_users.passthrough(...)), with request and response models indesktopaccountingapi.types. - Money as
decimal.Decimal, dates asdatetime.date, timestamps as timezone-awaredatetime.datetimewith the offset QuickBooks reported. - Local API key validation,
for_end_user(), automatic idempotency keys, retries that followDaapi-Should-Retry, and long-polling of requests that are still running after a sync timeout. - Cursor pagination with one page of read-ahead and
CursorExpiredErrorprogress fields. - Async mode through
enqueueaccessors returning request handles. - Typed errors with every field of the API's error object, plus
ErrorCodeandErrorTypeconstants. - Raw responses through
with_raw_response. - Standard Webhooks signature verification (
client.webhooks.verifyanddesktopaccountingapi.webhooks).