Skip to content

The compatibility contract ​

We deliver the most QuickBooks Online compatibility we can, and we're honest about the rest. Reads are almost entirely compatible; writes are where QuickBooks Desktop's quirks show up. Every partial or unsupported case below comes with the exact fix.

This page is the full entity matrix — every QBO Accounting API entity we track, whether live, planned, or a documented gap. Jump to an entity from the tables below, or read IDs, SyncToken & sparse updates for the cross-cutting rules.

Entity matrix ​

Every QBO Accounting API entity we track, with an honest verdict and a one-line Desktop reality check. Use this page to answer "does TenkeyBridge cover what I use?" in under a minute.

Legend — Full: works unchanged · Partial: common cases work, see the entity section · Full (read-only): reads work; no write path · Planned: real Desktop analog, not yet shipped · Stretch: lower-priority planned · Never: no honest Desktop equivalent · Future: platform ops after the entity matrix.

Sandbox — Yes: works in your sandbox · Read-only: seeded rows only · —: not in the sandbox; test it against a real company file.

transactions ​

EntityVerdictSandboxHeadline
TaxPaymentPartial—Read-only view of Desktop sales tax payment checks (SalesTaxPaymentCheck). QuickBooks Online offers TaxPayment only in AU, CA and UK; TenkeyBridge serves it for US Desktop sales tax payment checks.

Sales ​

EntityVerdictSandboxHeadline
CreditMemoPartialYesInvoice twin — Balance and RemainingCredit both map from Desktop CreditRemaining
CustomerFullYesSub-customer maps to Desktop Customer:Job
EstimatePartialYesSame shape as Invoice; expiration date and active/closed status, no acceptance tracking
InvoicePartialYesLine model, tax, discount, linked txns, and custom fields
ItemPartialYesOne Item becomes several Desktop item types
PaymentPartialYesApplying a payment to specific invoices
RefundReceiptPartialYesRefunds of applied credits by card (ARRefundCreditCard) or by check (a Check applied to the credit memo); not general item-line refunds
SalesReceiptPartialYesSame as Invoice + deposit account

Purchasing ​

EntityVerdictSandboxHeadline
BillFullYesAccount vs. item expense lines map cleanly
BillPaymentPartialYesFans out to BillPaymentCheck / BillPaymentCreditCard by PayType
InventoryAdjustmentPartialYesQuantity adjustments to inventory items — create, read, update (header and lines) and delete
PurchasePartialYesFans out to Check / CreditCardCharge / CreditCardCredit by PaymentType
PurchaseOrderPartialYesNon-posting; item lines only — never touches the GL on either side
VendorFullYes1099 / tax-ID field naming only
VendorCreditFullYesBill mirror image — expense and item lines

Banking ​

EntityVerdictSandboxHeadline
AccountFullYesAccountType / AccountSubType enum mapping
DepositPartialYesUndeposited Funds → bank via LinkedTxn or manual DepositLineDetail
JournalEntryPartialYesDebit/Credit lines split across Desktop JournalDebitLine / JournalCreditLine
TransferFullYesBank-to-bank transfer between two accounts

Lists & config ​

EntityVerdictSandboxHeadline
ClassFullYesHierarchical Class list; SubClass/FullyQualifiedName from Desktop Sublevel/FullName
CompanyCurrencyFull (read-only)YesDesktop CurrencyQuery list; empty when multicurrency is off (statusCode 3250, confirmed live, and 3170 both normalized to [])
CustomerTypeFull (read-only)YesRead/query only — QBO CustomerType is read-only via the API; Desktop hierarchy is not exposed
EmployeePartialYesRead/query plus create and update of core HR fields (live-verified); DisplayName is derived from first/middle/last; SSN is write-only and never returned; pay rates and other payroll fields out of scope
ExchangeRatePartialYesProjected from CurrencyRet.ExchangeRate + AsOfDate; one row per currency that carries a rate; empty when multicurrency is off (statusCode 3250, confirmed live, and 3170 both normalized to [])
PaymentMethodPartialYesCreate only — Desktop has no PaymentMethodMod; Type coarsens Desktop's fine enum to CREDIT_CARD
TaxAgencyFull (read-only)YesRead-only projection — Vendors referenced by any ItemSalesTax.TaxVendorRef (no native Desktop TaxAgency list)
TaxCodeFullYesDesktop SalesTaxCode list (TAX/NON); Name max 3 chars (fail-loud, never truncated); a plain taxable/non-taxable flag pair, not a rate container
TaxRatePartialYesRead like QBO; create and update are a TenkeyBridge extension over Desktop ItemSalesTaxAdd/Mod (QBO has no TaxRate write). Sales-tax groups are not projected as TaxRates.
TermPartialYesMerged StandardTerms + DateDrivenTerms; Type STANDARD
TimeActivityPartialYesEmployee and vendor time; Duration compiled from Hours/Minutes or a StartTime/EndTime span

Company ​

EntityVerdictSandboxHeadline
BudgetPartialRead-onlyRead-only, synthesized from BudgetSummaryReportQuery — qbXML has no Budget record, so Id is synthetic and deterministic ({fiscalYear}-{BudgetType}, e.g. 2026-ProfitAndLoss) with SyncToken pinned to "0", SELECT * probes the current calendar year ±1 against both statement types, and budget writes stay in the QuickBooks Desktop UI forever
CompanyInfoFull (read-only)YesRead-only; Id equals the Company ID, no Desktop write path
PreferencesPartialYesRead-only singleton (Id=1); honest subset of Desktop PreferencesQuery groups

Never — no Desktop equivalent ​

EntityVerdictSandboxHeadline
AttachableNever—Unsupported on QuickBooks Desktop, with no opt-in and no workaround inside TenkeyBridge. qbXML has no attachments interface (the word 'Attachment' does not occur anywhere in the qbxmlops130 message set), and TenkeyBridge does not store files itself.
CreditCardPaymentNever—QBO's 'pay down a credit card' transaction has no Desktop record of its own — Desktop books the same event as a Transfer or a Check against the card account, so there is nothing to project a CreditCardPayment from.
DepartmentNever—QuickBooks Desktop has no Department (location) list, so Department is unsupported by default. Opt in per company file with Departments from classes and Department becomes an alias of Class (DepartmentRef on a transaction becomes its header ClassRef).
JournalCodeNever—France-only QBO regulatory feature; not applicable to Desktop — 'JournalCode' occurs nowhere in the qbxmlops130 message set.
RecurringTransactionNever—Desktop's memorized transactions are not exposed by qbXML — there is no message to list, read, create, or execute them.
ReimburseChargeNever—QBO's billable-charge object has no Desktop record behind it — in qbXML, billability is a status flag (BillableStatus) on the source expense/item/time lines, never a standalone charge you can query.
TagNever—QBO Tags (and Tag classifications) are a cloud-only labeling feature — QuickBooks Desktop has no free-form tag object, and its nearest dimensional list (Class) is already exposed as Class.
TaxClassificationNever—Cloud automated-sales-tax concept with no Desktop counterpart — Desktop's tax model is explicit tax items and codes, all of which TenkeyBridge exposes.
VendorTypeNever—QBO has no VendorType entity to be compatible with — Desktop's vendor-type list (VendorTypeAdd/Query) has no counterpart in the QBO Accounting API, so TenkeyBridge does not expose it.

Platform ​

EntityVerdictSandboxHeadline
BatchFullYesBatch envelope — up to 30 create/update/delete/query operations in one request, executed sequentially with per-item faults.
CDCFull (read-only)YesChange Data Capture — one poll returns changed + deleted records across entities.
EntitlementsPartialYesGET /v3/company/{companyId}/entitlements — a static, honest capability document for a QuickBooks Desktop company (no per-file subscription data).
ReportsPartialRead-onlyForty-eight QBO-compatible reports served from QuickBooks Desktop: 24 translated live from Desktop's report engine (summary, detail, aging, balance, sales and sales-tax reports), 22 Desktop extension reports with no QBO twin, and two composites, AccountList (built from Desktop's account list) and CashFlow (built from several queries). CashFlow is derived, not translated: Desktop's SDK has no cash-flow report, so it is computed from Profit and Loss and two Trial Balances and checked against Desktop before it is returned.
TaxServicePartialYesPOST /taxservice/taxcode creates tax rates and the tax code that groups them (Desktop ItemSalesTaxAdd then ItemSalesTaxGroupAdd). The TaxCodeId is a sales tax group, used on a sales form as TxnTaxDetail.TxnTaxCodeRef.

Entity detail ​

TaxPayment ​

Verdict: Partial · Read: Partial · Write: None — Read-only view of Desktop sales tax payment checks (SalesTaxPaymentCheck). QuickBooks Online offers TaxPayment only in AU, CA and UK; TenkeyBridge serves it for US Desktop sales tax payment checks.

Sandbox: —

QBO fieldSupportNotes
PaymentDateFullDesktop TxnDate. Emitted as PaymentDate, the key in Intuit's Read and Query samples; Intuit's attribute table names it TxnDate.
PaymentAmountFullDesktop Amount.
PaymentAccountRefFullDesktop BankAccountRef.
DescriptionFullDesktop Memo.
RefundFullAlways false: a Desktop sales tax payment check is a payment, and a refund is recorded as a Deposit.

Everything else listed as full works unchanged.

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for TaxPayment. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Tax agency, check number and per-item amounts — UNSUPPORTED_BY_DESKTOP: SalesTaxPaymentCheck carries a payee agency, RefNumber and per-item lines, but QBO's TaxPayment has no field for them, so they are not returned.
  • create — UNSUPPORTED_BY_DESKTOP: QBO's TaxPayment is read-only via the API; TenkeyBridge matches that. Record the payment in QuickBooks Desktop.
  • update — UNSUPPORTED_BY_DESKTOP: QBO's TaxPayment is read-only via the API; TenkeyBridge matches that.
  • delete — UNSUPPORTED_BY_DESKTOP: QBO's TaxPayment is read-only via the API; TenkeyBridge matches that.

CreditMemo ​

Verdict: Partial · Read: Full · Write: Partial — Invoice twin — Balance and RemainingCredit both map from Desktop CreditRemaining

Sandbox: Yes

QBO fieldSupportNotes
ClassRefFullHeader-level class (Desktop ClassRef, read and write). Line-level ClassRef is separate.
DepartmentRefNoneUNMAPPED_FIELD — UNMAPPED_FIELD by default. With Departments from classes turned on for the company file, the gateway maps it to the header ClassRef (written to it, read from it); sending a different ClassRef as well is a 400.
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the transaction header. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to CreditMemo records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). The mechanism was verified live on QuickBooks Enterprise 24 (2026-09-30) on Customer, Vendor, Item, Employee, Invoice and PurchaseOrder; this entity follows the same path.
CustomerMemoFullMaps to Desktop CustomerMsgRef by FullName; the message must already exist as a Desktop customer message. Updatable via sparse update (#55).
LinePartialSame DetailType support as Invoice: SalesItemLineDetail, GroupLineDetail, and DescriptionOnly map cleanly; DiscountLineDetail names a Desktop discount item by DiscountLineDetail.ItemRef (value = ListID, or name = full name; PercentBased + DiscountPercent sends RatePercent, an amount discount sends Amount). With no ItemRef but a DiscountAccountRef, the gateway uses the one active discount item that posts to that account; none or several is MISSING_REQUIRED_DESKTOP_ITEM, and a discount item is never created for you. SubTotalLineDetail needs no ItemRef: the gateway uses the company's active subtotal item (the one named Subtotal if there are several, else the first), and only when there is none it creates an item named Subtotal (it has no account and posts nothing); a subtotal line is sent without Amount, Qty or Rate because Desktop computes the amount and refuses one. Other DetailTypes return UNSUPPORTED_BY_DESKTOP. Lines read back in Desktop's own order; a group line reads as GroupLineDetail with its component lines in GroupLineDetail.Line (each with its own Id) and the bundle total as Amount; a subtotal line reads as SubTotalLineDetail; Desktop discount lines read back as SalesItemLineDetail lines (never DiscountLineDetail) referencing the discount item, with a negative Amount (verified live). An unknown Line.Id returns 3120 (REST 610); a malformed one returns 3000. Updates carrying Line send the full line table, as QBO requires: a line with the Id a read returned keeps that Desktop line (Id = TxnLineID, stable across updates), a line without an Id (or Id 0 / empty) is added, and any existing line omitted from the array is deleted. Send the whole array on sparse updates too; an Id listed twice is rejected (2010). Fields you omit from a line that carries an Id are filled from the current record before the full line is sent (Desktop would otherwise blank them), and line ClassRef and ServiceDate are written (ServiceDate not on Estimate). Re-sending an existing group line returns 422 (UNSUPPORTED_BY_DESKTOP); omit it to delete it and add a new group line without an Id. The Ids on a group's component lines (GroupLineDetail.Line[].Id) are read-only: they identify what was read and are not accepted as line Ids on a write.
TxnTaxDetailPartialTxnTaxCodeRef selects the tax item (Desktop ItemSalesTaxRef). TotalTax alone is accepted but not sent: QuickBooks then applies the customer's default sales tax item (or the company default) and computes the tax itself, so the tax on read can differ from the TotalTax you sent. When it differs, create/update responses carry a non-fatal X-TKB-Warning header (not emitted for /batch items). On read, TaxLine carries a NetAmountTaxable that is derived, not read: the sum of the Tax-coded line Amounts (discount lines are not subtracted, verified live), returned only when it reproduces Desktop's SalesTaxTotal to the cent and omitted otherwise (custom tax codes, or a discount whose taxability is unknown). Updatable via sparse update (#55).
LinkedTxnPartialRead-only; reflects applied payments/credits, ignored on write — same as QBO.
TotalAmtFullRead-only, mapped from Desktop TotalAmount.
BalanceFullRead-only remaining unapplied credit; mapped from Desktop CreditRemaining.
RemainingCreditFullRead-only — total credit still available to apply. A distinct QBO field, not an alias of Balance; both map from Desktop's single CreditRemaining since Desktop has only the one figure.
ApplyTaxAfterDiscountNoneUNSUPPORTED_BY_DESKTOP — Desktop credit memos have no ApplyTaxAfterDiscount toggle. Control tax via per-line TaxCodeRef / TxnTaxDetail.TxnTaxCodeRef instead.

Everything else listed as full works unchanged.

Full fields
  • CustomerRef
  • TxnDate
  • BillAddr
  • ShipAddr
  • PONumber
  • SalesTermRef
  • DueDate
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for CreditMemo. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Line.DiscountLineDetail without an ItemRef or a single matching DiscountAccountRef item (bare-percentage or amount-off discounts) — MISSING_REQUIRED_DESKTOP_ITEM: Desktop discounts are items. Send DiscountAccountRef and the gateway uses the one active discount item that posts to that account; with no account, no match or several matches the call is refused and nothing is guessed, because a wrong item would post the discount to the wrong account. Create a discount item through the Item API (DesktopItemType Discount) or in QuickBooks (Lists > Item List > New > Discount) and pass it as DiscountLineDetail.ItemRef by value (ListID) or name (full name). See COMPATIBILITY.md.
  • Nested/grouped line types other than SalesItemLineDetail, DiscountLineDetail, GroupLineDetail, and DescriptionOnly — UNSUPPORTED_BY_DESKTOP: These line types do not map to QuickBooks Desktop sales transactions. Remove them or flatten to a supported DetailType. See COMPATIBILITY.md.
  • ApplyTaxAfterDiscount supplied — UNSUPPORTED_BY_DESKTOP: Desktop credit memos have no ApplyTaxAfterDiscount toggle. Control tax via per-line TaxCodeRef / TxnTaxDetail.TxnTaxCodeRef instead.

Customer ​

Verdict: Full · Read: Full · Write: Full — Sub-customer maps to Desktop Customer:Job

Sandbox: Yes

QBO fieldSupportNotes
ParentRefFullBecomes a Desktop Job under the parent; FullName is colon-delimited.
SalesTermRefFullDesktop TermsRef.
DefaultTaxCodeRefFullDesktop SalesTaxCodeRef.
AcctNumFullDesktop AccountNumber.
BalanceFullRead-only, as in QBO.
BalanceWithJobsFullRead-only; Desktop TotalBalance.
FullyQualifiedNameFullRead-only, derived by Desktop.
JobFullRead-only, derived by Desktop Sublevel.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the record. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to Customer records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). Verified live on QuickBooks Enterprise 24 (2026-09-30).
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
PreferredPaymentMethodRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
PreferredDeliveryMethodNoneUNSUPPORTED_BY_DESKTOP — Desktop has no delivery-method preference. Remove the field from your payload.
TaxableNoneUNSUPPORTED_BY_DESKTOP — Desktop customers have no taxable flag; set tax codes on transactions instead.
PrintOnCheckNameNoneUNSUPPORTED_BY_DESKTOP — Desktop CustomerRet has no PrintAs element (unlike Vendor/Employee).
MobileNoneUNSUPPORTED_BY_DESKTOP — Desktop CustomerRet has no separate mobile-phone element beyond Phone/AltPhone/Fax.
WebAddrNoneUNSUPPORTED_BY_DESKTOP — Desktop CustomerRet has no website element.

Everything else listed as full works unchanged.

Full fields
  • DisplayName
  • CompanyName
  • Title
  • GivenName
  • MiddleName
  • FamilyName
  • PrimaryEmailAddr
  • PrimaryPhone
  • AlternatePhone
  • Fax
  • BillAddr
  • ShipAddr
  • Notes
  • Active
  • CustomerTypeRef

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Customer. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Estimate ​

Verdict: Partial · Read: Full · Write: Partial — Same shape as Invoice; expiration date and active/closed status, no acceptance tracking

Sandbox: Yes

QBO fieldSupportNotes
ClassRefFullHeader-level class (Desktop ClassRef, read and write). Line-level ClassRef is separate.
DepartmentRefNoneUNMAPPED_FIELD — UNMAPPED_FIELD by default. With Departments from classes turned on for the company file, the gateway maps it to the header ClassRef (written to it, read from it); sending a different ClassRef as well is a 400.
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
DueDateFullDesktop has one estimate due date, which is also QBO's ExpirationDate; both names read back with the same value. Sending both with different values: on update the one that differs from the current record wins (ExpirationDate if both changed); on create ExpirationDate wins.
ExpirationDateFullMaps to the Desktop estimate DueDate. Read and write.
TxnStatusPartialMaps to Desktop IsActive: Pending = active, Closed = inactive (Mark as inactive). Closed means only that the estimate is marked inactive in Desktop; an estimate converted to an invoice still reads as Pending. Accepted and Rejected have no Desktop state and return UNSUPPORTED_BY_DESKTOP. Not independently verified against a live EstimateRet yet.
CustomerMemoFullMaps to Desktop CustomerMsgRef by FullName; the message must already exist as a Desktop customer message. Updatable via sparse update (#55).
LinePartialSame DetailType support as Invoice (for an item that has a cost, Desktop treats an Estimate line's Rate as cost plus markup, MarkupRatePercent, so Amount can differ from Qty x Rate; verified live): SalesItemLineDetail, GroupLineDetail, and DescriptionOnly map cleanly; line-level ClassRef and ServiceDate are populated on read; DiscountLineDetail names a Desktop discount item by DiscountLineDetail.ItemRef (value = ListID, or name = full name; PercentBased + DiscountPercent sends RatePercent, an amount discount sends Amount). With no ItemRef but a DiscountAccountRef, the gateway uses the one active discount item that posts to that account; none or several is MISSING_REQUIRED_DESKTOP_ITEM, and a discount item is never created for you. SubTotalLineDetail needs no ItemRef: the gateway uses the company's active subtotal item (the one named Subtotal if there are several, else the first), and only when there is none it creates an item named Subtotal (it has no account and posts nothing); a subtotal line is sent without Amount, Qty or Rate because Desktop computes the amount and refuses one. Other DetailTypes return UNSUPPORTED_BY_DESKTOP. Lines read back in Desktop's own order; a group line reads as GroupLineDetail with its component lines in GroupLineDetail.Line (each with its own Id) and the bundle total as Amount; a subtotal line reads as SubTotalLineDetail; Desktop discount lines read back as SalesItemLineDetail lines (never DiscountLineDetail) referencing the discount item, with a negative Amount (verified live). An unknown Line.Id returns 3120 (REST 610); a malformed one returns 3000. Updates carrying Line send the full line table, as QBO requires: a line with the Id a read returned keeps that Desktop line (Id = TxnLineID, stable across updates), a line without an Id (or Id 0 / empty) is added, and any existing line omitted from the array is deleted. Send the whole array on sparse updates too; an Id listed twice is rejected (2010). Fields you omit from a line that carries an Id are filled from the current record before the full line is sent (Desktop would otherwise blank them), and line ClassRef and ServiceDate are written (ServiceDate not on Estimate). Re-sending an existing group line returns 422 (UNSUPPORTED_BY_DESKTOP); omit it to delete it and add a new group line without an Id. The Ids on a group's component lines (GroupLineDetail.Line[].Id) are read-only: they identify what was read and are not accepted as line Ids on a write.
TxnTaxDetailPartialTxnTaxCodeRef on write selects the tax item (Desktop ItemSalesTaxRef). TotalTax alone is accepted but not sent: QuickBooks then applies the customer's default sales tax item (or the company default) and computes the tax itself, so the tax on read can differ from the TotalTax you sent. When it differs, create/update responses carry a non-fatal X-TKB-Warning header (not emitted for /batch items). On read: TotalTax, TxnTaxCodeRef, and TaxLine[{Amount, TaxLineDetail{TaxRateRef, TaxPercent}}]. NetAmountTaxable is derived, not read: the sum of the Tax-coded line Amounts (discount lines are not subtracted, verified live), returned only when it reproduces Desktop's SalesTaxTotal to the cent and omitted otherwise (custom tax codes, or a discount whose taxability is unknown). Updatable via sparse update (#55).
LinkedTxnFullPopulated on read from LinkedTxn (IncludeLinkedTxns automatic) — progress-invoicing links when present. Links to Desktop-only transaction types (SalesOrder, ItemReceipt, …) are omitted rather than guessed at. Accepted and ignored on write.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the transaction header. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to Estimate records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). The mechanism was verified live on QuickBooks Enterprise 24 (2026-09-30) on Customer, Vendor, Item, Employee, Invoice and PurchaseOrder; this entity follows the same path.
TotalAmtFullRead-only, computed by Desktop as Subtotal + SalesTaxTotal (not independently verified live — see issue #38 report).
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
AcceptedByNoneUNSUPPORTED_BY_DESKTOP — Desktop estimates have no accepted-by field. Track acceptance in your own system.
AcceptedDateNoneUNSUPPORTED_BY_DESKTOP — Desktop estimates have no accepted-date field. Track acceptance in your own system.

Everything else listed as full works unchanged.

Full fields
  • CustomerRef
  • TxnDate
  • BillAddr
  • ShipAddr
  • PONumber
  • SalesTermRef
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Estimate. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Line.DiscountLineDetail without an ItemRef or a single matching DiscountAccountRef item (bare-percentage or amount-off discounts) — MISSING_REQUIRED_DESKTOP_ITEM: Desktop discounts are items. Send DiscountAccountRef and the gateway uses the one active discount item that posts to that account; with no account, no match or several matches the call is refused and nothing is guessed, because a wrong item would post the discount to the wrong account. Create a discount item through the Item API (DesktopItemType Discount) or in QuickBooks (Lists > Item List > New > Discount) and pass it as DiscountLineDetail.ItemRef by value (ListID) or name (full name). See COMPATIBILITY.md.
  • Nested/grouped line types other than SalesItemLineDetail, DiscountLineDetail, GroupLineDetail, and DescriptionOnly — UNSUPPORTED_BY_DESKTOP: These line types do not map to QuickBooks Desktop sales transactions. Remove them or flatten to a supported DetailType. See COMPATIBILITY.md.

Invoice ​

Verdict: Partial · Read: Full · Write: Partial — Line model, tax, discount, linked txns, and custom fields

Sandbox: Yes — Group, subtotal and discount item lines are emulated, including group component lines on read.

QBO fieldSupportNotes
ClassRefFullHeader-level class (Desktop ClassRef, read and write). Line-level ClassRef is separate.
DepartmentRefNoneUNMAPPED_FIELD — UNMAPPED_FIELD by default. With Departments from classes turned on for the company file, the gateway maps it to the header ClassRef (written to it, read from it); sending a different ClassRef as well is a 400.
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
CustomerMemoFullMaps to Desktop CustomerMsgRef by FullName; the message must already exist as a Desktop customer message. Updatable via sparse update (#55).
LinePartialSalesItemLineDetail, GroupLineDetail, and DescriptionOnly map cleanly; line-level ClassRef and ServiceDate are populated on read; DiscountLineDetail names a Desktop discount item by DiscountLineDetail.ItemRef (value = ListID, or name = full name; PercentBased + DiscountPercent sends RatePercent, an amount discount sends Amount). With no ItemRef but a DiscountAccountRef, the gateway uses the one active discount item that posts to that account; none or several is MISSING_REQUIRED_DESKTOP_ITEM, and a discount item is never created for you. SubTotalLineDetail needs no ItemRef: the gateway uses the company's active subtotal item (the one named Subtotal if there are several, else the first), and only when there is none it creates an item named Subtotal (it has no account and posts nothing); a subtotal line is sent without Amount, Qty or Rate because Desktop computes the amount and refuses one. Other DetailTypes return UNSUPPORTED_BY_DESKTOP. Lines read back in Desktop's own order; a group line reads as GroupLineDetail with its component lines in GroupLineDetail.Line (each with its own Id) and the bundle total as Amount; a subtotal line reads as SubTotalLineDetail; Desktop discount lines read back as SalesItemLineDetail lines (never DiscountLineDetail) referencing the discount item, with a negative Amount (verified live). An unknown Line.Id returns 3120 (REST 610); a malformed one returns 3000. Updates carrying Line send the full line table, as QBO requires: a line with the Id a read returned keeps that Desktop line (Id = TxnLineID, stable across updates), a line without an Id (or Id 0 / empty) is added, and any existing line omitted from the array is deleted. Send the whole array on sparse updates too; an Id listed twice is rejected (2010). Fields you omit from a line that carries an Id are filled from the current record before the full line is sent (Desktop would otherwise blank them), and line ClassRef and ServiceDate are written (ServiceDate not on Estimate). Re-sending an existing group line returns 422 (UNSUPPORTED_BY_DESKTOP); omit it to delete it and add a new group line without an Id. The Ids on a group's component lines (GroupLineDetail.Line[].Id) are read-only: they identify what was read and are not accepted as line Ids on a write.
TxnTaxDetailPartialTxnTaxCodeRef on write selects the tax item (Desktop ItemSalesTaxRef). TotalTax alone is accepted but not sent: QuickBooks then applies the customer's default sales tax item (or the company default) and computes the tax itself, so the tax on read can differ from the TotalTax you sent. When it differs, create/update responses carry a non-fatal X-TKB-Warning header (not emitted for /batch items). On read: TotalTax, TxnTaxCodeRef (from ItemSalesTaxRef), and TaxLine[{Amount, TaxLineDetail{TaxRateRef, TaxPercent}}] from SalesTaxTotal/ItemSalesTaxRef/SalesTaxPercentage. NetAmountTaxable is derived, not read: the sum of the Tax-coded line Amounts (discount lines are not subtracted, verified live), returned only when it reproduces Desktop's SalesTaxTotal to the cent and omitted otherwise (custom tax codes, or a discount whose taxability is unknown). Updatable via sparse update (#55).
LinkedTxnFullPopulated on read from LinkedTxn when IncludeLinkedTxns is requested (automatic). Desktop ReceivePayment → QBO Payment; links to Desktop-only transaction types (SalesOrder, ItemReceipt, …) are omitted rather than guessed at. Accepted and ignored on write — same as QBO treats it as read-only.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the transaction header. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to Invoice records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). Verified live on QuickBooks Enterprise 24 (2026-09-30).
TotalAmtFullRead-only, computed by Desktop as Subtotal + SalesTaxTotal.
BalanceFullRead-only, computed by Desktop as BalanceRemaining.
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
operation=voidPartialPOST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType Invoice), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QB Enterprise 24 (2026-09-29): the re-read shows amounts and line Qty zeroed, PrivateNote prefixed VOID: , TxnDate unchanged, a new SyncToken; re-proven 2026-09-30 with a memo (PrivateNote becomes VOID: <memo>, DocNumber kept, Balance 0); voiding again succeeds. The response is whatever Desktop returns on the post-void re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • CustomerRef
  • TxnDate
  • BillAddr
  • ShipAddr
  • ShipDate
  • ShipMethodRef
  • PONumber
  • SalesTermRef
  • DueDate
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Invoice. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Line.DiscountLineDetail without an ItemRef or a single matching DiscountAccountRef item (bare-percentage or amount-off discounts) — MISSING_REQUIRED_DESKTOP_ITEM: Desktop discounts are items. Send DiscountAccountRef and the gateway uses the one active discount item that posts to that account; with no account, no match or several matches the call is refused and nothing is guessed, because a wrong item would post the discount to the wrong account. Create a discount item through the Item API (DesktopItemType Discount) or in QuickBooks (Lists > Item List > New > Discount) and pass it as DiscountLineDetail.ItemRef by value (ListID) or name (full name). See COMPATIBILITY.md.
  • Nested/grouped line types other than SalesItemLineDetail, DiscountLineDetail, GroupLineDetail, and DescriptionOnly — UNSUPPORTED_BY_DESKTOP: These line types do not map to QuickBooks Desktop sales transactions. Remove them or flatten to a supported DetailType. See COMPATIBILITY.md.

Item ​

Verdict: Partial · Read: Partial · Write: Partial — One Item becomes several Desktop item types

Sandbox: Yes

QBO fieldSupportNotes
NameFullDesktop item Name is the leaf name (max 31 characters); FullyQualifiedName is derived.
FullyQualifiedNameFullRead-only; derived from the Desktop FullName. Echoed values on update are ignored.
ActiveFullDeactivate with a sparse update setting Active to false; items are never deleted.
TypePartialService, NonInventory, Inventory and Group read and write. Every Desktop item type reads, in get-by-id and in query results, with the nearest QBO Type and a read-only DesktopItemType: inventory assemblies read as Inventory, fixed assets as NonInventory, and other-charge, discount, subtotal, payment, sales tax and sales tax group items as Service. Type cannot change after creation. Category has no Desktop equivalent.
DesktopItemTypePartialTenkeyBridge extension (QBO has no such field). Read on every item: Service, NonInventory, Inventory, InventoryAssembly, FixedAsset, OtherCharge, Discount, Subtotal, Payment, SalesTax, SalesTaxGroup or Group. It is also the write selector for the families QBO has no Type for: Discount, OtherCharge, Subtotal, Payment and InventoryAssembly (send it with the matching Type: Service for the first four, Inventory for InventoryAssembly). A value that disagrees with Type, or one that tries to change on update, is a 400 with code 2010. SalesTax and SalesTaxGroup items cannot be written through the Item API (write a sales tax item through POST /taxrate and a sales tax group through POST /taxservice/taxcode); FixedAsset create is not supported.
ItemGroupDetailFullGroup items, in QBO's bundle shape: ItemGroupDetail.ItemGroupLine[{ItemRef, Qty}]. A TenkeyBridge write extension, since QBO itself cannot create bundles. A line with Qty omitted is sent with Qty 1. A create needs at least one line. On update, sending ItemGroupDetail replaces the whole component list (post back every line you want to keep); a sparse update that does not carry it, such as a rename, leaves the components alone; an explicitly empty ItemGroupLine array empties the group.
PrintGroupedItemsFullTenkeyBridge extension. Group items only: whether the component lines print on a transaction (Desktop IsPrintItemsInGroup).
ItemAssemblyDetailFullTenkeyBridge extension (inventory assemblies are a Desktop concept): ItemAssemblyDetail.ItemAssemblyLine[{ItemRef, Qty}], with the same rules as ItemGroupDetail (Qty omitted means 1). Create with Type Inventory and DesktopItemType InventoryAssembly plus the Inventory fields (IncomeAccountRef, ExpenseAccountRef, AssetAccountRef).
DiscountPercentFullTenkeyBridge extension. Discount items: a percentage discount (for example 10 for ten percent). Send it or UnitPrice (a flat amount), not both. A discount item needs IncomeAccountRef on create.
DepositToAccountRefFullTenkeyBridge extension. Payment items: the account the payment is deposited to. On update an omitted value keeps the stored account; null resets it to Undeposited Funds.
PaymentMethodRefFullTenkeyBridge extension. Payment items: the payment method.
TaxRatePartialTenkeyBridge extension. Read only, on SalesTax items: the rate as a percentage. Write sales tax items through the TaxRate endpoint (POST /taxrate) and sales tax groups through POST /taxservice/taxcode.
AgencyRefPartialTenkeyBridge extension. Read only, on SalesTax items: the tax agency vendor.
TaxRateRefsPartialTenkeyBridge extension. Read only, on SalesTaxGroup items: the member tax items.
DescriptionFullDesktop sales description: SalesOrPurchase/Desc, SalesAndPurchase/SalesDesc, or SalesDesc on Inventory.
UnitPriceFullDesktop sales price. Sent with up to five decimals. On a Desktop discount item a flat rate reads as UnitPrice and a percentage discount reads as DiscountPercent; both can be written on a discount item. On other-charge items it is the flat amount.
SkuPartialQBO Sku maps to Desktop ManufacturerPartNumber (max 31 characters) — Desktop has no first-class SKU field; barcode (Advanced Inventory) is intentionally not used. NonInventory and Inventory only; on a Service, Sku is refused with UNSUPPORTED_BY_DESKTOP.
TrackQtyOnHandPartialRead as true for Inventory. On write it must agree with Type (Inventory always tracks quantity; Service and NonInventory never do) or the request is refused.
QtyOnHandPartialRead for Inventory. Settable on create only (with InvStartDate); Desktop changes stock through inventory adjustments and transactions, so a different value on update is refused. Echoing the current value is accepted. QuantityOnHand on an Inventory or assembly create posts an opening-balance inventory adjustment in QuickBooks (dated the create day, Opening Balance Equity), and the item cannot be deleted until that adjustment is deleted. The sandbox does not emulate that adjustment.
InvStartDatePartialWrite-only, create only (Desktop InventoryDate). Not returned on read.
IncomeAccountRefFullService/NonInventory: SalesOrPurchase/AccountRef (one-sided) or SalesAndPurchase/IncomeAccountRef (two-sided). Inventory: IncomeAccountRef. Required on create for every writable type (Desktop rejects an item with no posting account; a missing one is a 400 with code 2020).
ExpenseAccountRefPartialInventory: COGSAccountRef (required on create). Two-sided Service/NonInventory (SalesAndPurchase): ExpenseAccountRef. One-sided Service/NonInventory have no expense account; sending one at creation makes the item two-sided, and on update it is refused for a sales-only item.
AssetAccountRefFullInventory only; required on create.
PurchaseDescFullInventory and two-sided Service/NonInventory (SalesAndPurchase). Purchase-side fields must be present at creation: Desktop cannot turn an existing sales-only item into a two-sided one, so adding them on update is refused with UNSUPPORTED_BY_DESKTOP.
PurchaseCostFullInventory and two-sided Service/NonInventory (SalesAndPurchase). Sent with up to five decimals. On create with QtyOnHand, Desktop values the opening stock at PurchaseCost.
PrefVendorRefFullInventory and two-sided Service/NonInventory.
ParentRefFullDesktop item hierarchy; sub-items are FullName paths (Parent:Child).
SubItemFullDerived from Desktop Sublevel; read-only.
ClassRefFullDesktop ClassRef.
SalesTaxCodeRefFullDesktop SalesTaxCodeRef. QBO values TAX and NON map to Desktop's built-in Tax and Non codes; any other value is a ListID. Wins over Taxable if both are sent.
TaxablePartialDerived on read only when the sales tax code is the built-in Tax or Non code (a custom code leaves it unset). On write, true/false selects the Tax/Non code.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the record. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to Item records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). Verified live on QuickBooks Enterprise 24 (2026-09-30).

Everything else listed as full works unchanged.

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Item. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Writing SalesTax or SalesTaxGroup items — UNSUPPORTED_BY_DESKTOP: These read through the Item API (with TaxRate, AgencyRef and TaxRateRefs) but cannot be written through the Item API.
    • Workaround: Write a sales tax item through the TaxRate endpoint (POST /taxrate) and a sales tax group through POST /taxservice/taxcode.
  • Creating or updating FixedAsset items — UNSUPPORTED_BY_DESKTOP: Fixed assets read as Type NonInventory with DesktopItemType FixedAsset. Writing them is not supported.
  • Category items, and turning a sales-only item into a two-sided one — UNSUPPORTED_BY_DESKTOP: Category has no Desktop equivalent. Desktop also rejects the SalesOrPurchase to SalesAndPurchase conversion (error 3170): include PurchaseDesc, PurchaseCost, ExpenseAccountRef or PrefVendorRef when a Service or NonInventory item is created.
  • Changing QtyOnHand after creation — UNSUPPORTED_BY_DESKTOP: Stock levels change only through inventory adjustments and transactions. (Changing Type after creation is a 400 Invalid Field Value, since a Desktop item's type is fixed.)

Payment ​

Verdict: Partial · Read: Full · Write: Partial — Applying a payment to specific invoices

Sandbox: Yes

QBO fieldSupportNotes
PaymentRefNumFullDesktop RefNumber: max 20 characters; longer values are rejected up front with a 400 fault (code 2050). Queryable: WHERE PaymentRefNum = '...' / IN (...) / LIKE 'CHK%' is pushed to QuickBooks as RefNumber / RefNumberFilter (DocNumber is accepted as an alias).
DepositToAccountRefFullOmit to use Desktop's Undeposited Funds preference, same as QBO.
LinePartialOne Line per Invoice (LinkedTxn[0] = the invoice; on write Amount = the total applied to the invoice, cash plus credit, which is the qbXML PaymentAmount; on read Amount = the cash only). Credits ride inside the invoice Line's LinkedTxn: credit memos applied to that invoice are extra LinkedTxn entries {TxnId, TxnType CreditMemo, Amount = credit applied, a number > 0} and are sent as SetCredit; a credit-only Line (no invoice) is rejected (2020), and so is a credit-only payment (credit memos with TotalAmt omitted or 0): live QB applies the credit and creates no payment, so there is nothing to return; it is refused up front with 422 UNSUPPORTED_BY_DESKTOP (apply credits together with a cash TotalAmt, or in QuickBooks). Read returns the applied credit memos as CreditMemo LinkedTxn entries (Amount positive) from the LinkedTxn Desktop nests inside AppliedToTxnRet (verified live on QB Enterprise 24: the nested LinkedTxn has TxnType CreditMemo and a negative Amount, AppliedToTxnRet/Amount is the cash only, and the request PaymentAmount is cash plus credit). Over-crediting returns 3210 ('The amount you entered is greater than the amount of the credit'). No Line means an unapplied payment (IsAutoApply=false). Deposit applications are not supported. AppliedToTxnRet.TxnDate/RefNumber/BalanceRemaining have no honest home in QBO Payment.Line.LinkedTxn ({TxnId, TxnType} only) and are not projected — re-fetch the invoice for balance/date/doc number.
UnappliedAmtFullRead-only; Desktop UnusedPayment.
CustomFieldPartialName/StringValue populated from Desktop custom fields (DataExt); DefinitionId not available from Desktop; read-only because QuickBooks does not allow a public custom field on a received payment (the definition is refused with status 3351, verified live 2026-10-15). Accepted and ignored on write.
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ProcessPaymentPartialAccepted and ignored — TenkeyBridge never initiates card processing.
TxnNumberNoneUNSUPPORTED_BY_DESKTOP — Desktop's internal auto-increment TxnNumber has no QBO equivalent and is not surfaced.
UnusedCreditsNoneUNSUPPORTED_BY_DESKTOP — Desktop UnusedCredits (unapplied credit memos available to the customer) is distinct from UnappliedAmt/UnusedPayment; no honest home on Payment — belongs on CreditMemo.Balance.

Everything else listed as full works unchanged.

Full fields
  • CustomerRef
  • ARAccountRef
  • TxnDate
  • TotalAmt
  • PaymentMethodRef
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Payment. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • operation=void — UNSUPPORTED_BY_DESKTOP: qbXML's TxnVoidType has no ReceivePayment value — a customer payment cannot be voided through the SDK (HTTP 400). Delete it (?operation=delete) and re-enter it if needed.

RefundReceipt ​

Verdict: Partial · Read: Partial · Write: Partial — Refunds of applied credits by card (ARRefundCreditCard) or by check (a Check applied to the credit memo); not general item-line refunds

Sandbox: Yes — The sample company has no refunds; create one against a CreditMemo via LinkedTxn. Card refunds and check refunds (a Check applied to the credit memo) are both emulated; a check refund needs credit left on the memo.

QBO fieldSupportNotes
DepositToAccountRefFullMaps to Desktop RefundFromAccountRef.
PaymentMethodRefFullCard refunds: must be a credit-card payment method in the company file. Check refunds: not applicable, a 2010 fault if sent.
RefundMethodFullTenkeyBridge extension (QBO has no such field). Check or CreditCard, default CreditCard so existing callers are unchanged. Check writes a Desktop Check applied to the credit memo (DepositToAccountRef is the bank account the check is drawn on, CustomerRef the payee); any other value is 2010. Reads return RefundMethod Check on refund checks and RefundMethod CreditCard on card refunds. A refund check is a RefundReceipt only: it is excluded from Purchase reads. Change data capture: deleted RefundReceipts are found by asking Desktop for deleted Checks as well as deleted card refunds, and a deleted record carries no link to a credit memo, so every deleted ordinary check is also reported as a deleted RefundReceipt. Treat a RefundReceipt deletion as a hint and confirm with a read.
ARAccountRefFullTenkeyBridge extension for check refunds: the Accounts Receivable account Desktop needs on the check's expense line (a check without it is refused). Optional over REST: when omitted the gateway uses the credit memo's own A/R account, else the company's A/R account.
PrintStatusPartialCheck refunds only. TenkeyBridge sends the check as not to be printed (Desktop would otherwise drop DocNumber); send NeedToPrint to queue it for printing, in which case Desktop ignores DocNumber.
TxnDateFullQueries scan Desktop's checks to find refund checks, so an unfiltered query on a file with 1000 or more checks faults rather than return a partial answer. Narrow by TxnDate.
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
LinePartialCheck refunds take exactly one Line (Desktop applies one credit memo per check; more is 2010) with Amount and LinkedTxn, and a refund larger than the credit memo's remaining credit is refused by Desktop with a 400 (3210 "The amount you entered is greater than the amount of the credit." for a partial over-refund, and 3210 with its own text when the memo is already fully refunded). Deleting the check restores the credit. Card refunds: LinkedTxn credit-memo applications → RefundAppliedToTxnAdd only. At least one Line is required on create, because Desktop's ARRefundCreditCardAdd requires one or more RefundAppliedToTxnAdd; an empty/missing Line array fails loud rather than sending Desktop an invalid request. SalesItemLineDetail / item-line refunds return UNSUPPORTED_BY_DESKTOP; use CreditMemo + payment/refund pair instead (see CreditMemo).
TotalAmtFullRead-only from Desktop TotalAmount.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.

Everything else listed as full works unchanged.

Full fields
  • CustomerRef
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for RefundReceipt. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Item-line RefundReceipt (SalesItemLineDetail etc.) — UNSUPPORTED_BY_DESKTOP: Desktop has no single transaction that is both the returned goods and the refund: its refund (ARRefundCreditCard) only pays back an existing credit, and a check refund is a separate Check applied to a credit memo. Two calls instead: (1) POST CreditMemo with the returned item lines; (2) POST RefundReceipt with DepositToAccountRef (the card/refund account) and Line[{Amount, LinkedTxn:[{TxnId: the CreditMemo Id, TxnType:CreditMemo}]}]. Refunds by check are supported with RefundMethod Check (a Check applied to the credit memo); cash refunds are recorded in QuickBooks (Customers > Refund).
  • Update / sparse update — UNSUPPORTED_BY_DESKTOP: Neither refund family (ARRefundCreditCard or Check applied to a credit memo) has an update path in this release. Delete and create again.

SalesReceipt ​

Verdict: Partial · Read: Full · Write: Partial — Same as Invoice + deposit account

Sandbox: Yes

QBO fieldSupportNotes
ClassRefFullHeader-level class (Desktop ClassRef, read and write). Line-level ClassRef is separate.
DepartmentRefNoneUNMAPPED_FIELD — UNMAPPED_FIELD by default. With Departments from classes turned on for the company file, the gateway maps it to the header ClassRef (written to it, read from it); sending a different ClassRef as well is a 400.
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
CustomerMemoFullMaps to Desktop CustomerMsgRef by FullName; the message must already exist as a Desktop customer message. Updatable via sparse update (#55).
LinePartialSalesItemLineDetail, GroupLineDetail, and DescriptionOnly map cleanly; line-level ClassRef and ServiceDate are populated on read; DiscountLineDetail names a Desktop discount item by DiscountLineDetail.ItemRef (value = ListID, or name = full name; PercentBased + DiscountPercent sends RatePercent, an amount discount sends Amount). With no ItemRef but a DiscountAccountRef, the gateway uses the one active discount item that posts to that account; none or several is MISSING_REQUIRED_DESKTOP_ITEM, and a discount item is never created for you. SubTotalLineDetail needs no ItemRef: the gateway uses the company's active subtotal item (the one named Subtotal if there are several, else the first), and only when there is none it creates an item named Subtotal (it has no account and posts nothing); a subtotal line is sent without Amount, Qty or Rate because Desktop computes the amount and refuses one. Other DetailTypes return UNSUPPORTED_BY_DESKTOP. Lines read back in Desktop's own order; a group line reads as GroupLineDetail with its component lines in GroupLineDetail.Line (each with its own Id) and the bundle total as Amount; a subtotal line reads as SubTotalLineDetail; Desktop discount lines read back as SalesItemLineDetail lines (never DiscountLineDetail) referencing the discount item, with a negative Amount (verified live). An unknown Line.Id returns 3120 (REST 610); a malformed one returns 3000. Updates carrying Line send the full line table, as QBO requires: a line with the Id a read returned keeps that Desktop line (Id = TxnLineID, stable across updates), a line without an Id (or Id 0 / empty) is added, and any existing line omitted from the array is deleted. Send the whole array on sparse updates too; an Id listed twice is rejected (2010). Fields you omit from a line that carries an Id are filled from the current record before the full line is sent (Desktop would otherwise blank them), and line ClassRef and ServiceDate are written (ServiceDate not on Estimate). Re-sending an existing group line returns 422 (UNSUPPORTED_BY_DESKTOP); omit it to delete it and add a new group line without an Id. The Ids on a group's component lines (GroupLineDetail.Line[].Id) are read-only: they identify what was read and are not accepted as line Ids on a write.
TxnTaxDetailPartialTxnTaxCodeRef on write selects the tax item (Desktop ItemSalesTaxRef). TotalTax alone is accepted but not sent: QuickBooks then applies the customer's default sales tax item (or the company default) and computes the tax itself, so the tax on read can differ from the TotalTax you sent. When it differs, create/update responses carry a non-fatal X-TKB-Warning header (not emitted for /batch items). On read: TotalTax, TxnTaxCodeRef, and TaxLine[{Amount, TaxLineDetail{TaxRateRef, TaxPercent}}]. NetAmountTaxable is derived, not read: the sum of the Tax-coded line Amounts (discount lines are not subtracted, verified live), returned only when it reproduces Desktop's SalesTaxTotal to the cent and omitted otherwise (custom tax codes, or a discount whose taxability is unknown). Updatable via sparse update (#55).
LinkedTxnPartialDesktop SalesReceiptQueryRq has no IncludeLinkedTxns and SalesReceiptRet carries no LinkedTxnRet (qbXML 13.0) — never populated on read. Accepted and ignored on write.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the transaction header. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to SalesReceipt records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). The mechanism was verified live on QuickBooks Enterprise 24 (2026-09-30) on Customer, Vendor, Item, Employee, Invoice and PurchaseOrder; this entity follows the same path.
TotalAmtFullRead-only; Desktop computes it.
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
operation=voidPartialPOST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType SalesReceipt), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QB Enterprise 24 (2026-09-29): the re-read shows amounts and line Qty zeroed, PrivateNote prefixed VOID: , TxnDate unchanged, a new SyncToken; voiding again succeeds. The response is whatever Desktop returns on the post-void re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • CustomerRef
  • TxnDate
  • BillAddr
  • ShipAddr
  • ShipDate
  • ShipMethodRef
  • PrivateNote
  • DepositToAccountRef

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for SalesReceipt. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Bill ​

Verdict: Full · Read: Full · Write: Full — Account vs. item expense lines map cleanly

Sandbox: Yes

QBO fieldSupportNotes
DocNumberFullDesktop RefNumber: max 20 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
SalesTermRefFullDesktop TermsRef.
LineFullAccount-based and item-based expense lines; other line types return UNSUPPORTED_BY_DESKTOP. Update: Line is the WHOLE table, as in QBO. A line sent with its Id (the Line.Id a read returned) keeps that line, a line with no Id is added, and a line you leave out is deleted. Omit Line to leave every line alone (a header-only update such as PrivateNote never touches lines); an empty array is refused (2020), and a repeated or unknown Id is refused (2010). A line cannot change type: sending an expense line's Id with an item detail (or the reverse) is 2010, so omit the Id to replace it. On an item line that sends Amount without Qty or UnitPrice, Desktop keeps the stored quantity and re-derives the unit cost, so the Amount you send is the Amount you read back (verified live, 2026-10-14). Item group lines read as their component item lines, in the group's place and in Desktop's order, each carrying the TenkeyBridge extension fields GroupLineId (the group's Id) and GroupItemRef. The Id on each of those component lines is read-only: it identifies what was read and is not accepted as a line Id on a write. A transaction that has item group lines refuses a Line update with UNSUPPORTED_BY_DESKTOP, because a table update would delete them; header-only updates still work. Both AccountBasedExpenseLineDetail.ClassRef and ItemBasedExpenseLineDetail.ClassRef are read/written, mapping to Desktop ExpenseLineAdd/ExpenseLineRet and ItemLineAdd/ItemLineRet ClassRef respectively. On ExpenseLineAdd, ClassRef sits in OSR position (AccountRef < Amount < Memo < CustomerRef < ClassRef).
TotalAmtFullRead-only, computed by Desktop as AmountDue.
BalanceFullRead-only. Desktop's BillRet.OpenAmount is the VENDOR's total open balance across every open bill for that vendor, not this bill's own remaining amount (verified live, 2026-09-24) — never used directly. Computed instead as 0 when IsPaid, else AmountDue minus whatever BillPaymentCheck/BillPaymentCreditCard/VendorCredit amounts LinkedTxn shows applied to this bill.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the transaction header. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to Bill records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). Verified live on QuickBooks Enterprise 24 (2026-09-30). Desktop never assigns a custom field to Bills (only Customer/Vendor/Employee/Item fields can be defined, and they surface on sales forms and purchase orders), so a Bill CustomField is always a 400 'not assigned to Bill records'.
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
LinkedTxnNoneUNSUPPORTED_BY_DESKTOP — QBO Bill has no paid-by LinkedTxn field. Desktop may return LinkedTxn when IncludeLinkedTxns is set, but there is no honest QBO home — not projected.
operation=voidPartialTenkeyBridge extension: QBO cannot void a Bill; QuickBooks Desktop can. POST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType Bill), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QuickBooks Enterprise 24 (2026-10-15). The re-read shows PrivateNote as VOID: <memo>, every amount and line Amount at 0.00 with the lines kept, TotalAmt 0.00, Balance 0.00 (Desktop reports it paid). A void does not reliably advance the SyncToken, so re-read the record instead of relying on it; voiding again is accepted. The response is whatever Desktop returns on the re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • VendorRef
  • TxnDate
  • DueDate
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Bill. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

BillPayment ​

Verdict: Partial · Read: Full · Write: Partial — Fans out to BillPaymentCheck / BillPaymentCreditCard by PayType

Sandbox: Yes

QBO fieldSupportNotes
PayTypeFullCheck → BillPaymentCheck*; CreditCard → BillPaymentCreditCard*. Updates: Check family only — qbXML has no BillPaymentCreditCardMod, so credit-card-family updates fail loud with UNSUPPORTED_BY_DESKTOP (#75); delete and re-create instead.
VendorRefFullMaps to Desktop PayeeEntityRef.
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
TotalAmtFullRead-only from Desktop Amount; write-ignored on create/update — neither BillPaymentCheckAdd nor BillPaymentCreditCardAdd has a top-level Amount element in the OSR, Desktop derives it from the summed AppliedToTxnAdd/PaymentAmount values instead.
CheckPaymentFullCheckPayment.BankAccountRef ⇄ BankAccountRef.
CreditCardPaymentFullCreditCardPayment.CCAccountRef ⇄ CreditCardAccountRef.
PrintStatusPartialCheck flavor only; ⇄ IsToBePrinted. On update, PrintStatus NeedToPrint or PrintComplete sets IsToBePrinted true or false (verified live, 2026-10-14); Desktop then holds no reference number, so DocNumber reads back empty, and PrintStatus and DocNumber cannot be sent together. BillPaymentCheckAdd requires IsToBePrinted OR RefNumber (Desktop OSR); when a create supplies neither PrintStatus nor DocNumber, TenkeyBridge synthesizes IsToBePrinted=false so the Add stays schema-valid.
LinePartialRequired — at least one; QuickBooks Desktop's BillPaymentCheckAdd/BillPaymentCreditCardAdd schema requires one or more AppliedToTxnAdd. Write: LinkedTxn bills → AppliedToTxnAdd; additional LinkedTxn VendorCredit → SetCredit (the Add request shape). Read: an applied vendor credit comes back nested as AppliedToTxnRet.LinkedTxn (TxnType VendorCredit, Amount negative — absolute value surfaced as QBO LinkedTxn.Amount), not SetCreditRet/SetCredit, which Desktop never returns on this Ret (verified live, 2026-09-28). Update (Check family): Line is the WHOLE application set, as in QBO, keyed on the bill (LinkedTxn[0].TxnId) and sent as AppliedToTxnMod. A bill left out of the array is unapplied (it reopens) and the payment total recomputes; a bill not yet applied is added. Omit Line to leave applications untouched (a header-only update such as PrivateNote never touches them). An empty array (2020) or a repeated bill (2010) is refused. A vendor credit is applied by adding a second LinkedTxn (TxnType VendorCredit) to the bill's line. A credit already applied stays applied when a later update omits it: Desktop keeps it on the bill, even after the payment is deleted. Credit-card-family payments cannot be updated at all, whatever PayType the body carries (UNSUPPORTED_BY_DESKTOP); delete and re-create.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
query pagingFullSTARTPOSITION and MAXRESULTS page across both Desktop families as one list: check payments, then credit card payments, each in Desktop order. A deep page costs two QuickBooks messages (the ids of every row the query selects, then the page by id) and faults with UNSUPPORTED_QUERY past 5000 rows in one family; narrow with a TxnDate or MetaData.LastUpdatedTime range.
operation=voidPartialPOST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType BillPaymentCheck / BillPaymentCreditCard, picked by a read of the record), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QB Enterprise 24 (2026-09-29, check family): amounts zeroed, the applied-to-bill Line entries are removed and the paid bills reopen, PrivateNote prefixed VOID: ; voiding again succeeds. The response is whatever Desktop returns on the post-void re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • TxnDate
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for BillPayment. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • PayType outside Check/CreditCard — UNSUPPORTED_BY_DESKTOP: Only Check and CreditCard map to Desktop bill-payment families.

InventoryAdjustment ​

Verdict: Partial · Read: Full · Write: Partial — Quantity adjustments to inventory items — create, read, update (header and lines) and delete

Sandbox: Yes — The sandbox stores adjustments but does not track quantity on hand, so item quantities never change.

QBO fieldSupportNotes
AdjustAccountRefFullDesktop AccountRef, the adjustment (offset) account. Required on create.
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050).
PrivateNoteFullDesktop Memo.
LinePartialDetailType ItemAdjustmentLineDetail with ItemRef and QtyDiff. QtyDiff is a signed change in quantity on hand (nonzero, at most 5 decimal places) and maps to Desktop QuantityAdjustment/QuantityDifference (never NewQuantity); quantity on hand changes by exactly that amount, verified live. The same item on two lines is refused (Desktop rejects it). Desktop's value elements (ValueAdjustment/NewValue) are not exposed for writing; Desktop's ValueDifference (quantity times the item's average cost, verified live) is returned read-only as Line.Amount; the Ret line is flat QuantityDifference/ValueDifference. Line.Description and Line.Amount cannot be written (Desktop has no line memo and computes the value from the item's average cost). Update: Line is the WHOLE table, as in QBO. A line sent with its Id keeps that line (QtyDiff changes it), a line with no Id is added, and a line you leave out is deleted. Omit Line to leave every line alone; an empty array is refused (2020), and a repeated or unknown Id is refused (2010). Posting a read back whole works: an Amount on an existing line that equals the value the read returned is ignored, while a different Amount is UNSUPPORTED_BY_DESKTOP. Updating a value adjustment line (one with no quantity change) is UNSUPPORTED_BY_DESKTOP.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
operation=voidPartialTenkeyBridge extension: QBO cannot void an InventoryAdjustment; QuickBooks Desktop can. POST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType InventoryAdjustment), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QuickBooks Enterprise 24 (2026-10-15). The re-read shows PrivateNote as VOID: <memo> and every line quantity change and value change at 0. The void reverses the stock effect, so the item QuantityOnHand returns to its value before the adjustment. A void does not reliably advance the SyncToken, so re-read the record instead of relying on it; voiding again is accepted. The response is whatever Desktop returns on the re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • TxnDate

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for InventoryAdjustment. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Absolute quantity (NewQuantity) and value adjustments — UNSUPPORTED_BY_DESKTOP: QBO's InventoryAdjustment only has QtyDiff (a delta), so Desktop's NewQuantity/NewValue forms have no QBO field to map from.

Purchase ​

Verdict: Partial · Read: Full · Write: Partial — Fans out to Check / CreditCardCharge / CreditCardCredit by PaymentType

Sandbox: Yes

QBO fieldSupportNotes
PaymentTypePartialCheck and CreditCard map to Desktop families; Cash is partial (maps to Check against the supplied bank/petty-cash AccountRef — Desktop has no separate cash purchase type). On update PaymentType is immutable across Desktop's families: a Check (or Cash) purchase cannot become a CreditCard one, or the reverse, and a charge cannot become a credit; the update is refused (2010). Delete it and create a new Purchase.
CreditFulltrue + PaymentType CreditCard → CreditCardCredit*; false/omitted → CreditCardCharge*. Immutable on update (changing it is refused, 2010).
AccountRefFullBank account (Check/Cash) or credit-card account (CreditCard).
EntityRefFullMaps to Desktop PayeeEntityRef (Customer/Vendor/Employee).
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A check that is still to be printed (PrintStatus NeedToPrint, which Desktop may default to when PrintStatus is omitted) silently ignores RefNumber, on create and on a sparse DocNumber update alike (200, but no number is stored); send PrintStatus PrintComplete with DocNumber. On a PrintComplete check a DocNumber update changes RefNumber (verified live 2026-09-30). A single quote is allowed; query it with a backslash escape.
PrintStatusPartialNeedToPrint/PrintComplete ⇄ IsToBePrinted (Check family).
LineFullAccountBasedExpenseLineDetail and ItemBasedExpenseLineDetail via shared Bill line machinery. Update: Line is the WHOLE table, as in QBO. A line sent with its Id (the Line.Id a read returned) keeps that line, a line with no Id is added, and a line you leave out is deleted. Omit Line to leave every line alone (a header-only update such as PrivateNote never touches lines); an empty array is refused (2020), and a repeated or unknown Id is refused (2010). A line cannot change type: sending an expense line's Id with an item detail (or the reverse) is 2010, so omit the Id to replace it. On an item line that sends Amount without Qty or UnitPrice, Desktop keeps the stored quantity and re-derives the unit cost, so the Amount you send is the Amount you read back (verified live, 2026-10-14). Item group lines read as their component item lines, in the group's place and in Desktop's order, each carrying the TenkeyBridge extension fields GroupLineId (the group's Id) and GroupItemRef. The Id on each of those component lines is read-only: it identifies what was read and is not accepted as a line Id on a write. A transaction that has item group lines refuses a Line update with UNSUPPORTED_BY_DESKTOP, because a table update would delete them; header-only updates still work. Applies to Check, CreditCardCharge and CreditCardCredit alike.
TotalAmtFullRead-only from Desktop Amount.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
query pagingFullSTARTPOSITION and MAXRESULTS page across all three Desktop families as one list: Check, then CreditCardCharge, then CreditCardCredit, each in Desktop order, with refund checks left out of the count. A deep page costs two QuickBooks messages (the ids of every row the query selects, then the page by id) and faults with UNSUPPORTED_QUERY past 5000 rows in one family; narrow with a TxnDate or MetaData.LastUpdatedTime range.
operation=voidPartialPOST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType Check / CreditCardCharge / CreditCardCredit, picked by a read of the record), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QB Enterprise 24 (2026-09-29, Check family): amounts zeroed, PrintStatus becomes PrintComplete, and PrivateNote becomes VOID: <memo> GJE, RGJE created on MM/DD/YYYY — Desktop also posts two General Journal entries (live-proven again 2026-09-30, same-day and prior-month checks): a GJE dated the ORIGINAL check date that re-books the original check (Dr expense / Cr bank, memo 'For CHK voided on MM/DD/YYYY'), and a reversing RGJE (ref such as 7R, memo 'Reverse of GJE 7 -- ...') dated the VOID day that undoes it (Dr bank / Cr expense). The zeroed check keeps its TxnDate; the original period still shows the expense (via the GJE) and the reversal lands in the void day's period. Same-day voids net to zero on one date. Voiding again succeeds. Desktop's closing date is not exposed by PreferencesQuery; voids dated back to 2026-07-02 were accepted on the test file. CreditCardCharge/CreditCardCredit voids are not yet live-tested. The response is whatever Desktop returns on the post-void re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • TxnDate
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Purchase. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • A check applied to a credit memo (a refund check) — NOT_FOUND: A Desktop check applied to a credit memo is a QBO RefundReceipt, never a Purchase, so Purchase reads, deletes and updates skip it (GET by id is 610 Object Not Found). Use RefundReceipt.
  • PaymentType outside Check/Cash/CreditCard — UNSUPPORTED_BY_DESKTOP: Only Check, Cash (→Check), and CreditCard map to Desktop purchase families.

PurchaseOrder ​

Verdict: Partial · Read: Full · Write: Partial — Non-posting; item lines only — never touches the GL on either side

Sandbox: Yes

QBO fieldSupportNotes
ClassRefFullHeader-level class (Desktop ClassRef, read and write). Line-level ClassRef is separate.
DepartmentRefNoneUNMAPPED_FIELD — UNMAPPED_FIELD by default. With Departments from classes turned on for the company file, the gateway maps it to the header ClassRef (written to it, read from it); sending a different ClassRef as well is a 400.
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the transaction header. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to PurchaseOrder records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). Verified live on QuickBooks Enterprise 24 (2026-09-30).
LinePartialItemBasedExpenseLineDetail only (PurchaseOrderLineAdd). AccountBasedExpenseLineDetail returns UNSUPPORTED_BY_DESKTOP. ReceivedQty maps from ReceivedQuantity on read (read-only). Updates carrying Line send the full line table (PurchaseOrderLineMod): a line with the Id a read returned keeps that Desktop line (Id = TxnLineID), a line without an Id is added, and any existing line omitted from the array is deleted — send the whole array. Fields you omit from a line that carries an Id are filled from the current record; changing Qty or UnitPrice without Amount recomputes Amount, and restating Amount alone drops the inherited Rate and Qty. An Id that is not a line of the PO is a 400; an Id listed twice is 2010; an empty Line array is refused. Per-line IsManuallyClosed is not exposed. Verified live on QuickBooks Enterprise 24 (2026-09-30): the Id/new/deleted-line table, qty recompute and OSR child order are accepted.
TotalAmtFullRead-only from Desktop TotalAmount.
POStatusPartialOpen/Closed map to Desktop IsManuallyClosed, a Mod-only element: PurchaseOrderAdd has no IsManuallyClosed at all. Create with POStatus Open (or omitted) is one Add. Create with POStatus Closed is two QuickBooks requests, a PurchaseOrderAdd then a PurchaseOrderMod that closes it, and answers with the closed purchase order. If the close fails after the add, the purchase order exists and is open: the answer is 502 PO_CLOSE_FAILED naming its Id (and a requestid replay answers 502 REQUEST_OUTCOME_UNKNOWN naming the same Id, without re-sending), so do not retry the create; send an update with POStatus Closed. A sparse update with POStatus Closed sends IsManuallyClosed true (closing the whole PO); POStatus Open sends false (reopens). Any other value is a 400 (2010). An update without POStatus never re-sends the status. Verified live on QuickBooks Enterprise 24 (2026-09-30 and 2026-10-15): closing and reopening both work, and a Mod carrying the Add's own TxnID and EditSequence closes the new PO.
APAccountRefNoneUNSUPPORTED_BY_DESKTOP — Purchase orders are non-posting — there is no AP account on Desktop POs.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.

Everything else listed as full works unchanged.

Full fields
  • VendorRef
  • TxnDate
  • VendorAddr
  • ShipAddr
  • DueDate
  • ExpectedDate
  • ShipMethodRef
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for PurchaseOrder. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • AccountBasedExpenseLineDetail on a PurchaseOrder line — UNSUPPORTED_BY_DESKTOP: Desktop PO lines are item-only. Use ItemBasedExpenseLineDetail, or post a Bill/Purchase for account expenses.

Vendor ​

Verdict: Full · Read: Full · Write: Full — 1099 / tax-ID field naming only

Sandbox: Yes

QBO fieldSupportNotes
PrintOnCheckNameFullDesktop PrintAs.
TaxIdentifierFullMaps to Desktop VendorTaxIdent.
Vendor1099FullMaps to Desktop IsVendorEligibleFor1099.
BalanceFullRead-only, as in QBO.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the record. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to Vendor records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). Verified live on QuickBooks Enterprise 24 (2026-09-30).
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.

Everything else listed as full works unchanged.

Full fields
  • DisplayName
  • Active
  • CompanyName
  • Title
  • GivenName
  • MiddleName
  • FamilyName
  • BillAddr
  • PrimaryPhone
  • AlternatePhone
  • Fax
  • PrimaryEmailAddr
  • AcctNum

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Vendor. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

VendorCredit ​

Verdict: Full · Read: Full · Write: Full — Bill mirror image — expense and item lines

Sandbox: Yes

QBO fieldSupportNotes
DocNumberFullDesktop RefNumber: max 20 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
LineFullAccountBasedExpenseLineDetail and ItemBasedExpenseLineDetail via shared Bill line machinery. Update: Line is the WHOLE table, as in QBO. A line sent with its Id (the Line.Id a read returned) keeps that line, a line with no Id is added, and a line you leave out is deleted. Omit Line to leave every line alone (a header-only update such as PrivateNote never touches lines); an empty array is refused (2020), and a repeated or unknown Id is refused (2010). A line cannot change type: sending an expense line's Id with an item detail (or the reverse) is 2010, so omit the Id to replace it. On an item line that sends Amount without Qty or UnitPrice, Desktop keeps the stored quantity and re-derives the unit cost, so the Amount you send is the Amount you read back (verified live, 2026-10-14). Item group lines read as their component item lines, in the group's place and in Desktop's order, each carrying the TenkeyBridge extension fields GroupLineId (the group's Id) and GroupItemRef. The Id on each of those component lines is read-only: it identifies what was read and is not accepted as a line Id on a write. A transaction that has item group lines refuses a Line update with UNSUPPORTED_BY_DESKTOP, because a table update would delete them; header-only updates still work.
TotalAmtFullRead-only from Desktop CreditAmount.
BalanceFullRead-only remaining credit from Desktop CreditRemaining.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
operation=voidPartialTenkeyBridge extension: QBO cannot void a VendorCredit; QuickBooks Desktop can. POST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType VendorCredit), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QuickBooks Enterprise 24 (2026-10-15). The re-read shows PrivateNote as VOID: <memo> and every amount and line Amount at 0.00 with the lines kept. A void does not reliably advance the SyncToken, so re-read the record instead of relying on it; voiding again is accepted. The response is whatever Desktop returns on the re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • VendorRef
  • APAccountRef
  • TxnDate
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for VendorCredit. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Account ​

Verdict: Full · Read: Full · Write: Full — AccountType / AccountSubType enum mapping

Sandbox: Yes

QBO fieldSupportNotes
ParentRefFullBecomes a Desktop sub-account; FullName is colon-delimited.
AccountTypePartialMapped per the COMPATIBILITY.md appendix; unmapped values return 422 UNMAPPED_ACCOUNT_TYPE. Desktop-only Non-Posting appears on reads.
AccountSubTypePartialAccepted and ignored — Desktop has no account subtype; reads never return one.
FullyQualifiedNameFullRead-only, derived by Desktop.
SubAccountFullRead-only, derived from Desktop Sublevel.
CurrentBalanceFullRead-only, as in QBO.
CurrentBalanceWithSubAccountsFullRead-only; Desktop TotalBalance.
ClassificationPartialAccepted and ignored on writes; not returned on reads — derive it from AccountType client-side.
CustomFieldPartialName/StringValue populated from Desktop custom fields (DataExt); DefinitionId not available from Desktop; read-only for now. Accepted and ignored on write.
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
CashFlowClassificationNoneUNSUPPORTED_BY_DESKTOP — Desktop AccountRet.CashFlowClassification has no honest QBO Account field; omitted rather than guessed at.
SpecialAccountTypeNoneUNSUPPORTED_BY_DESKTOP — Desktop SpecialAccountType has no honest QBO Account field; omitted rather than guessed at.

Everything else listed as full works unchanged.

Full fields
  • Name
  • Active
  • AcctNum
  • Description

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Account. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Deposit ​

Verdict: Partial · Read: Full · Write: Partial — Undeposited Funds → bank via LinkedTxn or manual DepositLineDetail

Sandbox: Yes

QBO fieldSupportNotes
LinePartialWrite: LinkedTxn (PaymentTxnID path) for undeposited payments, or DepositLineDetail{Entity, AccountRef, PaymentMethodRef, CheckNum} for a manual line — one or the other, and a manual line's DepositLineDetail.AccountRef is required. Read: Desktop always returns DepositLineDetail; a line sourced from a deposited payment also carries LinkedTxn (TxnId/TxnType Payment) alongside it. Updates carrying Line send the full table as DepositLineMod: a line with the Id a read returned keeps that Desktop line, a line without an Id is added, and any existing line omitted from the array is deleted — send the whole array. A manual line has omitted fields filled from the current record; a line sourced from a deposited payment is re-sent as a bare TxnLineID (Desktop keeps the line and its payment link; re-sending PaymentTxnID is refused 3210 because the payment is already deposited), so edits to its amount, account and payer are not applied. A new line linking a still-undeposited payment is accepted. Verified live on QuickBooks Enterprise 24 (2026-09-30). CashBack is updated through the CashBack field, not Line. An Id that is not a line of the deposit is a 400; an Id listed twice is 2010; an empty Line array is refused.
CashBackPartialCreate and read map to Desktop CashBackInfo. An update that changes CashBack sends one CashBackInfoMod built from the current record with your AccountRef, Memo and Amount laid over it; an update that echoes the current CashBack unchanged sends nothing. CashBack: null removes it by setting its Amount to 0.00, and a read treats a cash back at 0.00 as none (Desktop keeps the element at 0.00 and the deposit total returns to the lines). Adding cash back to a deposit that has none is 422 UNSUPPORTED_BY_DESKTOP: Desktop cannot add cash back to an existing deposit, so re-create it. Verified live on QuickBooks Enterprise 24 (2026-10-15).
TotalAmtFullRead-only from Desktop DepositTotal.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
operation=voidPartialTenkeyBridge extension: QBO cannot void a Deposit; QuickBooks Desktop can. POST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType Deposit), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QuickBooks Enterprise 24 (2026-10-15). The re-read shows PrivateNote as VOID: <memo> and TotalAmt and every line Amount at 0.00 with the lines kept. A void does not reliably advance the SyncToken, so re-read the record instead of relying on it; voiding again is accepted. The response is whatever Desktop returns on the re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • DepositToAccountRef
  • TxnDate
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Deposit. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

JournalEntry ​

Verdict: Partial · Read: Full · Write: Partial — Debit/Credit lines split across Desktop JournalDebitLine / JournalCreditLine

Sandbox: Yes

QBO fieldSupportNotes
DocNumberFullDesktop RefNumber: max 11 characters. A longer value is rejected up front with a 400 fault (code 2050) rather than reaching Desktop. A single quote is allowed; query it with a backslash escape.
PrivateNoteFullMaps to Desktop Memo when the company file/OSR returns a header Memo on JournalEntry.
AdjustmentFullMaps to Desktop IsAdjustment.
LinePartialJournalEntryLineDetail only. Write splits by PostingType into JournalDebitLine / JournalCreditLine; read interleaves debit then credit lists. Debits must equal credits or create fails UNSUPPORTED_BY_DESKTOP before the Desktop round-trip. Updates carrying Line send the full table as JournalLineMod (TxnLineID + JournalLineType Debit/Credit): a line with the Id a read returned keeps that Desktop line (and may flip Debit/Credit), a line without an Id is added, and any existing line omitted from the array is deleted, so send the whole array. Fields you omit from a line that carries an Id (account, memo, entity, class) are filled from the current record. Balance is checked over the merged table before anything is sent. An Id that is not a line of the entry is a 400; an Id listed twice is 2010; an empty Line array is refused. Verified live on QuickBooks Enterprise 24 (2026-09-30): Debit/Credit flips and deletion of unlisted lines work. Desktop rejects a Mod whose existing lines are not in stored (ascending TxnLineID) order with 3290, and a read lists debit lines before credit lines, so the gateway sends existing lines in TxnLineID order whatever order you post them in. JournalEntryLineDetail.Entity: Type is filled by a name lookup (Customer, Vendor or Employee; other names have no Type). Desktop's line-level EntityRef carries no entity-kind flag, so the gateway asks QuickBooks which list owns the name (one extra round trip, cached per company for 15 minutes); if the lookup fails the line comes back with EntityRef only. A Type you send on write is accepted and not stored, and the answer to a create or update carries no Type on its lines (a read does).
TotalAmtFullRead-only aggregate when present; not independently computed.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
operation=voidPartialTenkeyBridge extension: QBO cannot void a JournalEntry; QuickBooks Desktop can. POST ?operation=void with {Id, SyncToken}: sends TxnVoidRq (TxnVoidType JournalEntry), then re-reads and returns the record. SyncToken is verified against the current EditSequence first (mismatch = 5010). Proven live on QuickBooks Enterprise 24 (2026-10-15). The re-read shows every line Amount at 0.00 with the lines kept, and VOID: before the memo of the first line only (the entry's own PrivateNote is unchanged). Desktop returns both lines on the debit side after a void, so a voided journal entry does not keep its per-line debit and credit sides. A void does not reliably advance the SyncToken, so re-read the record instead of relying on it; voiding again is accepted. The response is whatever Desktop returns on the re-read (nothing is fabricated).

Everything else listed as full works unchanged.

Full fields
  • TxnDate

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for JournalEntry. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Unbalanced journal entry (debits ≠ credits) — UNSUPPORTED_BY_DESKTOP: Desktop rejects unbalanced JEs. Balance debits and credits before create.
  • Line DetailType other than JournalEntryLineDetail — UNSUPPORTED_BY_DESKTOP: Only JournalEntryLineDetail maps to Desktop journal lines.

Transfer ​

Verdict: Full · Read: Full · Write: Full — Bank-to-bank transfer between two accounts

Sandbox: Yes

QBO fieldSupportNotes
FromAccountRefFullMaps to Desktop TransferFromAccountRef.
ToAccountRefFullMaps to Desktop TransferToAccountRef.
CurrencyRefNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.
ExchangeRateNoneUNSUPPORTED_BY_DESKTOP — Multicurrency not yet supported.

Everything else listed as full works unchanged.

Full fields
  • Amount
  • TxnDate
  • PrivateNote

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Transfer. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Delete — UNSUPPORTED_BY_DESKTOP: "Transfer" is not a valid qbXML TxnDelType enum value (verified live: status 3110), so delete is not wired for this entity.

Class ​

Verdict: Full · Read: Full · Write: Full — Hierarchical Class list; SubClass/FullyQualifiedName from Desktop Sublevel/FullName

Sandbox: Yes

QBO fieldSupportNotes
ParentRefFullBecomes a Desktop sub-class; FullName is colon-delimited (e.g. Services:West Coast).
FullyQualifiedNameFullRead-only, derived by Desktop from the parent chain.
SubClassFullRead-only, derived from Desktop Sublevel (true when Sublevel > 0).

Everything else listed as full works unchanged.

Full fields
  • Name
  • Active

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Class. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

CompanyCurrency ​

Verdict: Full (read-only) · Read: Full · Write: None — Desktop CurrencyQuery list; empty when multicurrency is off (statusCode 3250, confirmed live, and 3170 both normalized to [])

Sandbox: Yes

QBO fieldSupportNotes
CodeFullDesktop CurrencyCode.

Everything else listed as full works unchanged.

Full fields
  • Name
  • Active

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for CompanyCurrency. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Create, update, or delete — UNSUPPORTED_BY_DESKTOP: CompanyCurrency is read/query only — Desktop CurrencyAdd/Mod is out of scope for this stretch entity; QBO also treats company currency catalog as admin-managed.

CustomerType ​

Verdict: Full (read-only) · Read: Full · Write: None — Read/query only — QBO CustomerType is read-only via the API; Desktop hierarchy is not exposed

Sandbox: Yes

Every field works unchanged.

Full fields
  • Name
  • Active

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for CustomerType. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Create, update or deactivate — UNSUPPORTED_BY_DESKTOP: QBO's CustomerType is read-only via the API, and TenkeyBridge matches that. Desktop has CustomerTypeAdd but no CustomerTypeMod (verified live: a CustomerTypeMod is a qbXML parse error), so a type cannot be renamed or made inactive through the API at all; Desktop's hierarchy (ParentRef/Sublevel/FullName) has no QBO CustomerType fields and is not surfaced.
    • Workaround: Create, rename or deactivate the type in QuickBooks Desktop itself; the change shows up on the next read.

Employee ​

Verdict: Partial · Read: Full · Write: Partial — Read/query plus create and update of core HR fields (live-verified); DisplayName is derived from first/middle/last; SSN is write-only and never returned; pay rates and other payroll fields out of scope

Sandbox: Yes

QBO fieldSupportNotes
DisplayNamePartialRead-only: Desktop derives Name as GivenName MiddleName FamilyName (blanks skipped). A create/update naming a different DisplayName is refused with 400; one that matches is accepted and not sent. Create requires GivenName or FamilyName; DisplayName alone is refused (TenkeyBridge does not split it).
PrintOnCheckNameFullDesktop PrintAs.
ActiveFullDeactivate with a sparse update of Active:false. QBO deactivates employees rather than deleting them, and so does TenkeyBridge, even though Desktop can delete an employee that has no transactions.
PrimaryAddrFullDesktop's employee address has two street lines, City, State and PostalCode. Live limits: Line1/Line2 41 characters, City 31, PostalCode 13 (longer values are rejected up front with 2050; a State longer than Desktop's field fails with Desktop status 3110). Line3-Line5 and Country are rejected on write.
GenderFullMale or Female.
SSNPartialWrite-only: accepted as 123-45-6789 or 123456789 on create/update and never returned by Desktop (verified live: absent from Add, Mod, Get and Query responses). A masked value (XXX-XX-1234) is rejected on create and skipped on update, never written.
CustomFieldPartialDesktop custom fields (data extensions, OwnerID 0) on the record. Read: Name/StringValue (Type is always StringType; DefinitionId is not available from Desktop). Write: CustomField[{Name, StringValue}] is written as DataExtMod requests (on Desktop Mod both sets a first value and changes one; DataExtAdd fails once a value exists, and a new transaction inherits its customer's/vendor's value) keyed by the field Name, sent in ONE follow-up message after the record is saved; the field must already be defined in QuickBooks and assigned to Employee records, checked up front (400 CUSTOM_FIELD_UNKNOWN, nothing written). If the record saves but a custom field does not, the answer is 502 CUSTOM_FIELD_WRITE_FAILED naming the record Id and which fields applied or failed (never a silent 200); if everything writes but the read-back fails it is 502 CUSTOM_FIELD_READBACK_FAILED. A DefinitionId without a Name is a 400; only Type StringType is accepted. An update may carry only CustomField (SyncToken still checked). Line-level custom fields are not written. Values are limited to 30 characters and may not be empty or blank (400, nothing written). Verified live on QuickBooks Enterprise 24 (2026-09-30).
EmployeeNumberFullDesktop AccountNumber (the Account No. field on the employee record), up to 99 characters. Verified live on QuickBooks Enterprise 24 (2026-10-15).
TargetBonusFullDesktop TargetBonus, a money amount. Accepted on create and update without a payroll subscription (verified live on QuickBooks Enterprise 24, 2026-10-15); Desktop's create response omits it but reads return it.
AdditionalContactRefNoneUNSUPPORTED_BY_DESKTOP — Desktop AdditionalContactRef has no honest QBO Employee field; omitted rather than guessed at.

Everything else listed as full works unchanged.

Full fields
  • GivenName
  • MiddleName
  • FamilyName
  • Suffix
  • PrimaryPhone
  • Mobile
  • PrimaryEmailAddr
  • HiredDate
  • ReleasedDate
  • BirthDate

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Employee. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Delete — UNSUPPORTED_BY_DESKTOP: QBO employees are deactivated, not deleted. Desktop can delete an employee with no transactions, but TenkeyBridge keeps deactivate-only to match QBO (sparse update with Active:false).
  • Payroll fields — UNSUPPORTED_BY_DESKTOP: Pay rates, pay periods, wage items and other EmployeePayrollInfo content are license-gated in Desktop and not exposed for read or write.

ExchangeRate ​

Verdict: Partial · Read: Partial · Write: None — Projected from CurrencyRet.ExchangeRate + AsOfDate; one row per currency that carries a rate; empty when multicurrency is off (statusCode 3250, confirmed live, and 3170 both normalized to [])

Sandbox: Yes

QBO fieldSupportNotes
SourceCurrencyCodeFullDesktop CurrencyCode on the same CurrencyRet.
RateFullDesktop ExchangeRate.
AsOfDateFullDesktop AsOfDate when present; omitted when Desktop has none.
TargetCurrencyCodeNoneUNSUPPORTED_BY_DESKTOP — CurrencyRet has no per-row target; home currency lives on Preferences.CurrencyPrefs.HomeCurrency — never invented here.

Everything else listed as full works unchanged.

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for ExchangeRate. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • QBO-style keyed lookup GET /exchangerate?sourcecurrencycode=EUR[&asofdate=...] — UNSUPPORTED_QUERY: Real QBO keys ExchangeRate by sourcecurrencycode/asofdate query params. TenkeyBridge exposes it as a conventional Id-keyed resource (Id = Desktop currency ListID) with SELECT * queries — filter by SourceCurrencyCode client-side.
  • Create, update, or delete — UNSUPPORTED_BY_DESKTOP: ExchangeRate is read/query only — projected from CurrencyQuery, not a writable Desktop list.
  • Currencies without ExchangeRate on CurrencyRet — UNSUPPORTED_BY_DESKTOP: Projection skips CurrencyRets that do not carry ExchangeRate (home currency rows often omit it).

PaymentMethod ​

Verdict: Partial · Read: Full · Write: Partial — Create only — Desktop has no PaymentMethodMod; Type coarsens Desktop's fine enum to CREDIT_CARD | NON_CREDIT_CARD

Sandbox: Yes

QBO fieldSupportNotes
TypePartialDesktop PaymentMethodType (Visa, Cash, Check, …) coarsens to QBO CREDIT_CARD

Everything else listed as full works unchanged.

Full fields
  • Name
  • Active

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for PaymentMethod. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Update — UNSUPPORTED_BY_DESKTOP: Desktop has no PaymentMethodMod. Create a new PaymentMethod instead; list entities are not deleted — deactivate is also unavailable without Mod.

TaxAgency ​

Verdict: Full (read-only) · Read: Full · Write: None — Read-only projection — Vendors referenced by any ItemSalesTax.TaxVendorRef (no native Desktop TaxAgency list)

Sandbox: Yes

QBO fieldSupportNotes
DisplayNameFullVendor Name.
TaxTrackedOnPurchasesNoneUNSUPPORTED_BY_DESKTOP — No Desktop equivalent on the Vendor projection.
TaxTrackedOnSalesNoneUNSUPPORTED_BY_DESKTOP — No Desktop equivalent on the Vendor projection.

Everything else listed as full works unchanged.

Full fields
  • Active

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for TaxAgency. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Create or update — UNSUPPORTED_BY_DESKTOP: TaxAgency is a read-only projection of tax-vendor Vendors. Manage the Vendor (and ItemSalesTax.TaxVendorRef) in Desktop.

TaxCode ​

Verdict: Full · Read: Full · Write: Full — Desktop SalesTaxCode list (TAX/NON); Name max 3 chars (fail-loud, never truncated); a plain taxable/non-taxable flag pair, not a rate container

Sandbox: Yes

QBO fieldSupportNotes
NameFullDesktop limits Name to 3 characters. Longer names return 400 — never truncated.
DescriptionFullDesktop Desc.
TaxableFullDesktop IsTaxable.
SalesTaxRateListNoneUNMAPPED_FIELD — Desktop's US edition (QBD) does not have ItemSalesTaxRef/ItemPurchaseTaxRef on SalesTaxCode — the OSR marks both 'not in QBD' (they exist only for QBCA/QBUK/QBAU). US Desktop SalesTaxCode is just the taxable/non-taxable flag pair; rate association lives on ItemSalesTax items. Read TaxRate for rate values/agency. Never returned on read; writing it faults UNMAPPED_FIELD.

Everything else listed as full works unchanged.

Full fields
  • Active

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for TaxCode. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

TaxRate ​

Verdict: Partial · Read: Full · Write: Partial — Read like QBO; create and update are a TenkeyBridge extension over Desktop ItemSalesTaxAdd/Mod (QBO has no TaxRate write). Sales-tax groups are not projected as TaxRates.

Sandbox: Yes

QBO fieldSupportNotes
NameFullCreate and update. Max 31 characters, rejected up front with a 400 (2050). Required on create.
ActiveFullCreate and update (Desktop IsActive). Deactivate with a sparse update of Active false.
RateValueFullDesktop TaxRate percentage, a number from 0 to 100 kept to full precision (8.875 stays 8.875). Required on create. Create and update.
DescriptionFullDesktop ItemDesc. Create and update.
AgencyRefFullDesktop TaxVendorRef, the tax agency Vendor. Required on create.

Everything else listed as full works unchanged.

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for TaxRate. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Create or update through a QBO client — UNSUPPORTED_BY_DESKTOP: QBO rejects a TaxRate write, so an app written for QBO never sends one. TenkeyBridge accepts it as an extension (POST /taxrate), mapped to ItemSalesTaxAdd and ItemSalesTaxMod. A sparse update sends only the fields you changed.
    • Workaround: To create a rate together with the tax code that groups it, use POST /taxservice/taxcode.
  • ItemSalesTaxGroup as TaxRate — UNSUPPORTED_BY_DESKTOP: Sales-tax groups are not projected as TaxRate rows. Group members are themselves ItemSalesTax list items and appear as individual TaxRates via ItemSalesTaxQuery. A group is created through TaxService.

Term ​

Verdict: Partial · Read: Full · Write: Partial — Merged StandardTerms + DateDrivenTerms; Type STANDARD | DATE_DRIVEN dispatches which Desktop list; create-only

Sandbox: Yes

QBO fieldSupportNotes
TypeFullSTANDARD ⇄ StandardTerms*; DATE_DRIVEN ⇄ DateDrivenTerms*. Fixed at create — the whole entity is create-only, so this can never change via update.
DueDaysFullSTANDARD only — Desktop StdDueDays.
DiscountDaysFullSTANDARD only — Desktop StdDiscountDays.
DiscountPercentFullBoth types — Desktop DiscountPct.
DayOfMonthDueFullDATE_DRIVEN only.
DueNextMonthDaysFullDATE_DRIVEN only.
DiscountDayOfMonthFullDATE_DRIVEN only.

Everything else listed as full works unchanged.

Full fields
  • Name
  • Active

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for Term. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Not supported ​

  • Update any Term field — UNSUPPORTED_BY_DESKTOP: qbXML has no StandardTermsMod or DateDrivenTermsMod — Desktop's OSR only defines Add and Query for both Terms lists. Term write support is create-only.
  • Deactivate a Term — UNSUPPORTED_BY_DESKTOP: Deactivation is a Mod-only operation (IsActive via StandardTermsMod/DateDrivenTermsMod), which doesn't exist in qbXML. Impossible via the SDK, not just unimplemented here.

TimeActivity ​

Verdict: Partial · Read: Full · Write: Partial — Employee and vendor time; Duration compiled from Hours/Minutes or a StartTime/EndTime span

Sandbox: Yes

QBO fieldSupportNotes
NameOfFullEmployee or Vendor; anything else returns 2010. Desktop does not store the kind: reads ask QuickBooks which name list owns the entry (one extra round trip, cached per company file for 15 minutes) and fail with 502 NAME_LOOKUP_FAILED if that lookup fails. An entry whose name is neither an employee nor a vendor carries no NameOf.
EmployeeRefFullRequired when NameOf is Employee or omitted; Desktop EntityRef. Missing value returns 2020. Sending EmployeeRef together with VendorRef returns 2010.
VendorRefFullVendor time: send NameOf Vendor with VendorRef (or just VendorRef). Desktop EntityRef. Vendor time must be NotBillable, which is what we send when BillableStatus is omitted; Billable vendor time is refused by QuickBooks.
ItemRefFullDesktop ItemServiceRef.
DepartmentRefNoneUNMAPPED_FIELD — UNMAPPED_FIELD by default. With Departments from classes turned on for the company file, the gateway maps it to the header ClassRef (written to it, read from it); sending a different ClassRef as well is a 400.
DescriptionFullDesktop Notes.
BillableStatusPartialBillable, NotBillable, HasBeenBilled; any other value returns 2010. Desktop is stricter than QBO here: "Billable" requires BOTH CustomerRef and ItemRef on the same entry, or QuickBooks rejects the save ("Billable activities must have a customer:job and service item"). Send both, or mark the entry NotBillable. Omitted on create → we emit NotBillable explicitly (QBO's omitted-default); Desktop would otherwise infer Billable from CustomerRef alone. A sparse update that never mentions BillableStatus carries the stored value forward instead — the default only applies to genuinely-absent-on-create.
HoursFullExplicit duration input; wins over StartTime/EndTime when both are present.
MinutesFullExplicit duration input; wins over StartTime/EndTime when both are present.
StartTimeFullAlternate duration input paired with EndTime; Desktop only stores the computed Duration, not the span itself.
EndTimeFullAlternate duration input paired with StartTime; must be after it, or 2010.
BreakHoursPartialOnly valid alongside StartTime/EndTime; supplying it with Hours/Minutes returns 2010.
BreakMinutesPartialOnly valid alongside StartTime/EndTime; supplying it with Hours/Minutes returns 2010.
HourlyRatePartialAccepted and ignored. Desktop time tracking does not take a Rate through this interface: QuickBooks answers "The field Rate is not supported by this implementation" (probe PD5), so it is never sent.
CostRatePartialAccepted and ignored. Desktop time tracking has no cost-rate element.
TaxablePartialAccepted and ignored — Desktop time tracking has no taxable flag.
CustomFieldPartialDesktop TimeTrackingQueryRq has no OwnerID element (qbXML 13.0), so custom fields are never returned for time entries. Accepted and ignored on write.
CurrencyRefPartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
ExchangeRatePartialAccepted and ignored — Desktop manages this itself; multi-currency mapping lands later.
PayrollItemRefNoneUNSUPPORTED_BY_DESKTOP — Desktop PayrollItemWageRef has no QBO Accounting TimeActivity equivalent (QBO Payroll is a separate product).
TxnNumberNoneUNSUPPORTED_BY_DESKTOP — Desktop's internal auto-increment TxnNumber has no QBO equivalent and is not surfaced.

Everything else listed as full works unchanged.

Full fields
  • TxnDate
  • CustomerRef
  • ClassRef

Query support ​

The shared query language — SELECT * / COUNT(*) / column lists, AND-joined WHERE, ORDERBY, STARTPOSITION / MAXRESULTS — documented in Query support, which also lists what QuickBooks filters and what TenkeyBridge filters for TimeActivity. Syntax errors and unknown properties return 4000; queries that would scan past 5,000 records return UNSUPPORTED_QUERY.

Budget ​

Verdict: Partial · Read: Partial · Write: None — Read-only, synthesized from BudgetSummaryReportQuery — qbXML has no Budget record, so Id is synthetic and deterministic ({fiscalYear}-{BudgetType}, e.g. 2026-ProfitAndLoss) with SyncToken pinned to "0", SELECT * probes the current calendar year ±1 against both statement types, and budget writes stay in the QuickBooks Desktop UI forever

Sandbox: Read-only — Served from a fixed capture of the sample company, which has no budgets: SELECT * is an empty list and GET by id is 610.

QBO fieldSupportNotes
NamePartialSynthesized from the id — "FY2026 Profit and Loss Budget", "FY2026 Balance Sheet Budget". Desktop budget names are not exposed by BudgetSummaryReportQuery, so this is a stable derived label, not the name you typed in QuickBooks.
StartDateFullThe 1st of the first budgeted month column, e.g. January 1 for a calendar-year budget. Never taken from the report's ReportSubtitle prose, which can state a mid-year as-of window (a year-to-date subtitle) even though the report's money columns cover the full fiscal year.
EndDateFullThe last day of the last budgeted month column, e.g. December 31 for a calendar-year budget — not the report's ReportSubtitle, which can understate this (a year-to-date subtitle names today's date, not the budget's fiscal-year end).
BudgetTypePartialProfitAndLoss (ProfitAndLossBudgetOverview) and BalanceSheet (BalanceSheetBudgetOverview). Desktop's three budget-vs-actual report types mix actuals and variance into the same table — those are reports, not budgets, and are not exposed here.
BudgetEntryTypePartialAlways "Monthly". Desktop budgets are entered by month, and the report is requested with monthly columns; Yearly entry is not represented.
ActivePartialAlways true. A budget that returns amounts exists; Desktop has no inactive flag for budgets.
BudgetDetailPartialOne entry per account × budgeted month, with BudgetDate (first of the month), Amount, and AccountRef. Months with no budgeted amount are omitted, matching QBO — a Desktop 0.00 cell is indistinguishable from "not budgeted".
MetaDataNoneDesktop's budget report carries no created/modified stamp, so CreateTime/LastUpdatedTime are omitted rather than invented.

Everything else listed as full works unchanged.

Query support ​

  • query — WHERE on anything but Id, ORDERBY, COUNT(*), column lists — UNSUPPORTED_QUERY: Budget is not a Desktop list, so there is no query filter to compile a WHERE clause into. Only WHERE Id = '{fiscalYear}-{BudgetType}' is accepted; it resolves to the same single report request GET by id uses. ORDERBY, COUNT(*) and column lists are rejected too.
    • Workaround: Fetch the window with SELECT * FROM Budget and filter the (at most six) rows client-side.

Not supported ​

  • query — SELECT * cannot enumerate every budget — UNSUPPORTED_QUERY: qbXML cannot list budgets: BudgetSummaryReportQueryRq requires a FiscalYear. SELECT * FROM Budget probes a fixed window — the current calendar year, the one before it, and the one after it — against both statement types (six report requests in one round-trip) and returns the ones that come back with budgeted amounts. A budget outside that window is invisible to SELECT *. Because the probe is speculative, the query never faults on an individual report's status: a company with no budgets gets an empty list, exactly as QBO answers. GET by id has one report and does surface its status.
    • Workaround: Fetch it by id: GET /v3/company/{companyId}/budget/{fiscalYear}-{BudgetType}, e.g. 2023-ProfitAndLoss, which issues exactly one report request for that year.
  • query — WHERE on anything but Id, ORDERBY, COUNT(*), column lists — UNSUPPORTED_QUERY: Budget is not a Desktop list, so there is no query filter to compile a WHERE clause into. Only WHERE Id = '{fiscalYear}-{BudgetType}' is accepted; it resolves to the same single report request GET by id uses. ORDERBY, COUNT(*) and column lists are rejected too.
    • Workaround: Fetch the window with SELECT * FROM Budget and filter the (at most six) rows client-side.
  • read — Class and Customer budget breakdowns — UNSUPPORTED_BY_DESKTOP: Desktop's BudgetCriterion also offers AccountsAndClasses and AccountsAndCustomers, which QBO would carry as ClassRef/CustomerRef on BudgetDetail. TenkeyBridge sends the Accounts criterion only: the extra breakdowns come back as report rows whose sole identity is a display label, and splitting a label into account + class would be a guess.
    • Workaround: Read the class- or customer-level figures from the budget reports in QuickBooks Desktop directly.
  • read — budget-vs-actual report types — UNSUPPORTED_BY_DESKTOP: BalanceSheetBudgetVsActual, ProfitAndLossBudgetVsActual and ProfitAndLossBudgetPerformance mix actuals and variance columns into the budget table. Those are reports, not budgets; putting non-budget money in BudgetDetail.Amount would be wrong.
    • Workaround: Pull the actuals with the Reports surface (ProfitAndLoss / BalanceSheet) and compare them against this Budget client-side.
  • read — AccountRef.value on an unrecognised row label — UNSUPPORTED_BY_DESKTOP: Desktop report rows name an account by display label only; there is no ListID anywhere in the report. TenkeyBridge resolves the label against the chart of accounts it fetches in the same round-trip — matching FullName (stripping a leading "{AccountNumber} · " from every colon-separated segment of a qualified/subaccount label, e.g. "40100 · Construction Income:40130 · Labor Income") and, failing that, the leaf segment's own AccountNumber — but a label neither can explain yields AccountRef with a name and no value rather than an invented id.
  • create — UNSUPPORTED_BY_DESKTOP: Permanent: qbXML has no BudgetAdd. Budget setup stays in the Desktop UI — QBO's own Budget entity is read-only too, so no QBO-compatible client loses anything here.
  • update — UNSUPPORTED_BY_DESKTOP: Permanent: qbXML has no BudgetMod. Budget changes stay in the Desktop UI.
  • delete — UNSUPPORTED_BY_DESKTOP: Permanent: qbXML has no BudgetDel.

CompanyInfo ​

Verdict: Full (read-only) · Read: Full · Write: None — Read-only; Id equals the Company ID, no Desktop write path

Sandbox: Yes

QBO fieldSupportNotes
LegalNameFullDesktop LegalCompanyName.
CompanyAddrFullDesktop Address.
CustomerCommunicationAddrFullDesktop CompanyAddressForCustomer — an independent address, not a copy of CompanyAddr; absent if Desktop has none on file.
LegalAddrFullDesktop LegalAddress.
PrimaryPhoneFullDesktop Phone.
FiscalYearStartMonthFullDesktop FirstMonthFiscalYear; both sides use English month names (live-verified "January").
WebAddrFullDesktop CompanyWebSite as WebAddr.URI; absent when no website is on file.
CustomerCommunicationEmailAddrFullDesktop CompanyEmailForCustomer as Address; absent when none is on file.
NameValuePartialTaxForm and CompanyType only, as Name/Value pairs when present; EIN is never returned.
CountryFullDesktop Address.Country, passed through as free text — no normalization, and often blank on US company files.
CompanyStartDateNoneUNSUPPORTED_BY_DESKTOP — No Desktop CompanyRet equivalent — omitted rather than guessed at; never returned.
SupportedLanguagesNoneUNSUPPORTED_BY_DESKTOP — No Desktop CompanyRet equivalent — omitted rather than guessed at; never returned.
DefaultTimeZoneNoneUNSUPPORTED_BY_DESKTOP — No Desktop CompanyRet equivalent — omitted rather than guessed at; never returned.
EmployerIdNoneUNMAPPED_FIELD — Desktop has an EIN, but TenkeyBridge deliberately does not expose it: it is sensitive and not needed to sync books. Never returned or accepted.

Everything else listed as full works unchanged.

Full fields
  • CompanyName
  • Email

Query support ​

  • Any WHERE clause, ORDERBY, COUNT(*), column list, or STARTPOSITION/MAXRESULTS — UNSUPPORTED_QUERY: CompanyQueryRq takes no filters and always returns the single open company record. Use SELECT * FROM CompanyInfo.

Not supported ​

  • Create, update, or delete — UNSUPPORTED_BY_DESKTOP: CompanyInfo is read/query only — there is exactly one company record, and Desktop has no API to create or modify it.
  • Any WHERE clause, ORDERBY, COUNT(*), column list, or STARTPOSITION/MAXRESULTS — UNSUPPORTED_QUERY: CompanyQueryRq takes no filters and always returns the single open company record. Use SELECT * FROM CompanyInfo.

Preferences ​

Verdict: Partial · Read: Partial · Write: None — Read-only singleton (Id=1); honest subset of Desktop PreferencesQuery groups

Sandbox: Yes

QBO fieldSupportNotes
AccountingInfoPrefsPartialClassTrackingPerTxn/ClassTrackingPerTxnLine ← IsUsingClassTracking (same bool projected to both); TrackDepartments is false, or true when the company file has Departments from classes turned on (Department is then an alias of Class). UseAccountNumbers ← IsUsingAccountNumbers; BookCloseDate ← AccountingPreferences.ClosingDate when the file has one; FirstMonthOfFiscalYear ← company FirstMonthFiscalYear and TaxYearMonth ← FirstMonthIncomeTaxYear (read from a companion company query; omitted if that query fails).
CurrencyPrefsPartialMultiCurrencyEnabled ← IsMultiCurrencyOn; HomeCurrency ← HomeCurrencyRef.
TimeTrackingPrefsPartialWorkWeekStartDate ← FirstDayOfWeek only.
TaxPrefsPartialUsingSalesTax true when SalesTaxPreferences is present on PreferencesRet; no other TaxPrefs fields.
SalesFormsPrefsPartialUsingPriceLevels ← PriceLevels.IsUsingPriceLevels; DefaultShipMethodRef; AllowEstimates/UsingProgressInvoicing from JobsAndEstimatesPreferences. DefaultTerms never populated, because Desktop SalesAndCustomersPreferences carries no DefaultTermsRef (qbXML 13.0). IsAutoApplyingPayments has no honest QBO SalesFormsPrefs twin, and Desktop has no credits-specific auto-apply element, so AutoApplyCredit is omitted.
VendorAndPurchasesPrefsPartialUsingInventory ← IsUsingInventory only. DaysBillsAreDue / auto-discount have no honest QBO twin.
ReportPrefsPartialReportBasis ← SummaryReportBasis when Cash or Accrual.
ProductAndServicesPrefsPartialQuantityOnHand ← IsUsingInventory only; nothing else has a Desktop twin.
EmailMessagesPrefsNoneUNSUPPORTED_BY_DESKTOP — No honest PreferencesRet projection — omitted rather than guessed.
OtherPrefsNoneUNSUPPORTED_BY_DESKTOP — No honest PreferencesRet projection — omitted rather than guessed.

Everything else listed as full works unchanged.

Query support ​

  • Any WHERE clause, ORDERBY, COUNT(*), column list, or STARTPOSITION/MAXRESULTS — UNSUPPORTED_QUERY: PreferencesQueryRq takes no filters and always returns the single open company PreferencesRet. Use SELECT * FROM Preferences.

Not supported ​

  • Create, update, or delete — UNSUPPORTED_BY_DESKTOP: Preferences is read/query only in QBO and Desktop — there is no PreferencesAdd/Mod.
  • Any WHERE clause, ORDERBY, COUNT(*), column list, or STARTPOSITION/MAXRESULTS — UNSUPPORTED_QUERY: PreferencesQueryRq takes no filters and always returns the single open company PreferencesRet. Use SELECT * FROM Preferences.

Attachable ​

Verdict: Never · Read: None · Write: None — Unsupported on QuickBooks Desktop, with no opt-in and no workaround inside TenkeyBridge. qbXML has no attachments interface (the word 'Attachment' does not occur anywhere in the qbxmlops130 message set), and TenkeyBridge does not store files itself.

Sandbox: —

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: Desktop has no attachments API over qbXML — Attachable queries always fail loud.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: Re-audited 2026-07-29: zero occurrences of any attachment message (no AttachmentAdd/Query/Ref of any kind) across the full qbxmlops130 schema. Desktop's Attached Documents feature is UI-only; files attached in Desktop cannot be listed or fetched through TenkeyBridge.
    • Workaround: Store documents in your own system keyed by the TenkeyBridge entity Id, which is what QuickBooks Desktop integrations have always had to do. Branch on UNSUPPORTED_BY_DESKTOP and skip the Attachable call for Desktop-backed companies.
  • query — UNSUPPORTED_BY_DESKTOP: Desktop has no attachments API over qbXML — Attachable queries always fail loud.
  • create — UNSUPPORTED_BY_DESKTOP: Desktop has no attachments API over qbXML — attachments cannot be uploaded through TenkeyBridge.
  • update — UNSUPPORTED_BY_DESKTOP: Desktop has no attachments API over qbXML — attachments cannot be modified through TenkeyBridge.
  • delete — UNSUPPORTED_BY_DESKTOP: Desktop has no attachments API over qbXML — attachments cannot be deleted through TenkeyBridge.

CreditCardPayment ​

Verdict: Never · Read: None · Write: None — QBO's 'pay down a credit card' transaction has no Desktop record of its own — Desktop books the same event as a Transfer or a Check against the card account, so there is nothing to project a CreditCardPayment from.

Sandbox: —

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: No Desktop CreditCardPayment exists to query; card paydowns appear under Transfer or Purchase.
    • Workaround: Query Transfer or Purchase and filter client-side by the credit card account.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: qbXML has no CreditCardPayment message. A card payment made in Desktop is stored as an ordinary Transfer (Transfer Funds) or a Check/Bill Payment whose target is the credit card liability account, and is indistinguishable from any other such transaction on read.
    • Workaround: Read the same events as Transfer (ToAccountRef = the credit card account) or as a Purchase of type Check. Both are live in TenkeyBridge.
  • query — UNSUPPORTED_BY_DESKTOP: No Desktop CreditCardPayment exists to query; card paydowns appear under Transfer or Purchase.
    • Workaround: Query Transfer or Purchase and filter client-side by the credit card account.
  • create — UNSUPPORTED_BY_DESKTOP: TenkeyBridge will not guess which Desktop transaction to create in place of a CreditCardPayment.
    • Workaround: To pay down a card, create a Transfer with FromAccountRef = your bank account and ToAccountRef = the credit card account (or a Purchase/Check whose account line is the credit card account). That is what QuickBooks Desktop records for the same action.
  • update — UNSUPPORTED_BY_DESKTOP: No Desktop CreditCardPayment to update; update the Transfer or Purchase instead.
  • delete — UNSUPPORTED_BY_DESKTOP: No Desktop CreditCardPayment to delete; delete the Purchase instead (Transfer cannot be deleted through qbXML).

Department ​

Verdict: Never · Read: None · Write: None — QuickBooks Desktop has no Department (location) list, so Department is unsupported by default. Opt in per company file with Departments from classes and Department becomes an alias of Class (DepartmentRef on a transaction becomes its header ClassRef).

Sandbox: — — With Departments from classes on, the sandbox serves Department exactly as a real company file does (it is Class).

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

QBO fieldSupportNotes
IdPartialOnly with Departments from classes on: the Class ListID.
NamePartialOnly with Departments from classes on: the Class Name (31 characters max).
SubDepartmentPartialOnly with Departments from classes on: read-only, true when the class has a parent.
ParentRefPartialOnly with Departments from classes on: the parent class.
FullyQualifiedNamePartialOnly with Departments from classes on: the class full name (Parent:Child). Read-only.
ActivePartialOnly with Departments from classes on: class IsActive. Deactivate with Active:false.
SyncTokenPartialOnly with Departments from classes on: the class EditSequence.
MetaDataPartialOnly with Departments from classes on: class TimeCreated / TimeModified.

Everything else listed as full works unchanged.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: Off by default: no Department list to query. With Departments from classes on, SELECT ... FROM Department is run against Class and answered as Department; DepartmentRef in a transaction query means its ClassRef.
    • Workaround: Turn on Departments from classes, or query Class directly.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: Off by default. Re-audited 2026-07-29 against the qbxmlops130 message set: qbXML has no Department or Location list (no Add/Mod/Query message pair). The tokens only appear as plain STRTYPE fields (Employee.Department, FixedAsset.Location, Lead.Location), which are annotations, not a queryable list.
    • Workaround: Turn on Departments from classes for the company file (portal: company file page, Departments card; or PATCH /admin/v1/companies/{companyId}/settings with departmentsFromClasses: true). Department then reads your QuickBooks classes. See the Departments guide.
  • query — UNSUPPORTED_BY_DESKTOP: Off by default: no Department list to query. With Departments from classes on, SELECT ... FROM Department is run against Class and answered as Department; DepartmentRef in a transaction query means its ClassRef.
    • Workaround: Turn on Departments from classes, or query Class directly.
  • create — UNSUPPORTED_BY_DESKTOP: Off by default. With Departments from classes on, creating a Department creates a Class (ParentRef makes it a sub-class).
    • Workaround: Turn on Departments from classes, or create a Class.
  • update — UNSUPPORTED_BY_DESKTOP: Off by default. With Departments from classes on, an update edits the Class (sparse; Name, Active, ParentRef).
    • Workaround: Turn on Departments from classes, or update the Class.
  • delete — UNSUPPORTED_BY_DESKTOP: Always unsupported: a Desktop class cannot be deleted through qbXML. Deactivate with Active:false.
  • DepartmentRef on a transaction — UNSUPPORTED_BY_DESKTOP: With Departments from classes on it maps to the header ClassRef on Invoice, SalesReceipt, Estimate, CreditMemo, PurchaseOrder and TimeActivity. Sending a different ClassRef as well is a 400. Bill, Purchase, VendorCredit, JournalEntry, Deposit, RefundReceipt and Transfer have no header class in QuickBooks Desktop (class lives on the lines), so DepartmentRef there is a 422 that says to set ClassRef per line. Off: UNMAPPED_FIELD.
    • Workaround: Set ClassRef on each line for those transactions.

JournalCode ​

Verdict: Never · Read: None · Write: None — France-only QBO regulatory feature; not applicable to Desktop — 'JournalCode' occurs nowhere in the qbxmlops130 message set.

Sandbox: —

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: JournalCode is a France-locale QBO regulatory concept with no Desktop counterpart.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: Re-audited 2026-07-29: JournalCode is a France-locale QBO regulatory concept (mandatory journal coding for FEC compliance) with zero counterpart in qbXML — US Desktop has no such concept to map.
  • query — UNSUPPORTED_BY_DESKTOP: JournalCode is a France-locale QBO regulatory concept with no Desktop counterpart.
  • create — UNSUPPORTED_BY_DESKTOP: JournalCode is a France-locale QBO regulatory concept with no Desktop counterpart.
  • update — UNSUPPORTED_BY_DESKTOP: JournalCode is a France-locale QBO regulatory concept with no Desktop counterpart.
  • delete — UNSUPPORTED_BY_DESKTOP: JournalCode is a France-locale QBO regulatory concept with no Desktop counterpart.

RecurringTransaction ​

Verdict: Never · Read: None · Write: None — Desktop's memorized transactions are not exposed by qbXML — there is no message to list, read, create, or execute them.

Sandbox: —

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: Memorized transactions cannot be enumerated via qbXML — zero functional API surface.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: Re-audited 2026-07-29: no MemorizedTxn message of any kind exists in the qbxmlops130 message set. The only trace of the concept in the entire schema is a cosmetic 'memorizedTxn' row label in generic report output — a taxonomy tag, not an access path to a memorized transaction's contents or schedule.
    • Workaround: Manage memorized transactions in the Desktop UI. Transactions they generate ARE visible through TenkeyBridge once QuickBooks posts them — they arrive as ordinary invoices/bills/etc.
  • query — UNSUPPORTED_BY_DESKTOP: Memorized transactions cannot be enumerated via qbXML — zero functional API surface.
  • create — UNSUPPORTED_BY_DESKTOP: Memorized transactions cannot be created via qbXML; manage them in the Desktop UI.
  • update — UNSUPPORTED_BY_DESKTOP: Memorized transactions cannot be updated via qbXML; manage them in the Desktop UI.
  • delete — UNSUPPORTED_BY_DESKTOP: Memorized transactions cannot be deleted via qbXML; manage them in the Desktop UI.

ReimburseCharge ​

Verdict: Never · Read: None · Write: None — QBO's billable-charge object has no Desktop record behind it — in qbXML, billability is a status flag (BillableStatus) on the source expense/item/time lines, never a standalone charge you can query.

Sandbox: —

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: No standalone billable-charge object exists in qbXML to query; billability is line-level state on source transactions.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: Re-audited 2026-07-29: 'ReimburseCharge' occurs nowhere in the qbxmlops130 message set. Desktop models billability as a BillableStatus enum (Billable / NotBillable / HasBeenBilled) on expense and item lines of purchase transactions and on time entries — there is no separate queryable charge object to project one from.
    • Workaround: Read billable state where Desktop actually keeps it: TimeActivity exposes BillableStatus today, and billable purchase lines live on their source Bill/Purchase transactions.
  • query — UNSUPPORTED_BY_DESKTOP: No standalone billable-charge object exists in qbXML to query; billability is line-level state on source transactions.
  • create — UNSUPPORTED_BY_DESKTOP: ReimburseCharge is a QBO cloud concept with no Desktop qbXML analog; mark source lines Billable instead.
  • update — UNSUPPORTED_BY_DESKTOP: ReimburseCharge is a QBO cloud concept with no Desktop qbXML analog.
  • delete — UNSUPPORTED_BY_DESKTOP: ReimburseCharge is a QBO cloud concept with no Desktop qbXML analog.

Tag ​

Verdict: Never · Read: None · Write: None — QBO Tags (and Tag classifications) are a cloud-only labeling feature — QuickBooks Desktop has no free-form tag object, and its nearest dimensional list (Class) is already exposed as Class.

Sandbox: —

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: There is no Desktop tag list to query.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: qbXML has no Tag or TagClassification message; Desktop has no equivalent many-to-many labeling of transactions.
    • Workaround: Use Class for a single dimension per transaction line (live in TenkeyBridge), or keep your labels in your own system keyed by the TenkeyBridge entity Id.
  • query — UNSUPPORTED_BY_DESKTOP: There is no Desktop tag list to query.
  • create — UNSUPPORTED_BY_DESKTOP: Desktop cannot store tags, so none can be created.
  • update — UNSUPPORTED_BY_DESKTOP: Desktop cannot store tags, so none can be updated.
  • delete — UNSUPPORTED_BY_DESKTOP: Desktop cannot store tags, so none can be deleted.

TaxClassification ​

Verdict: Never · Read: None · Write: None — Cloud automated-sales-tax concept with no Desktop counterpart — Desktop's tax model is explicit tax items and codes, all of which TenkeyBridge exposes.

Sandbox: —

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: TaxClassification is part of QBO's automated sales tax stack; Desktop uses explicit tax items/codes instead.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: Re-audited 2026-07-29: 'TaxClassification' occurs nowhere in the qbxmlops130 message set. Desktop's tax machinery is ItemSalesTax / ItemSalesTaxGroup / SalesTaxCode — explicit records, not AST classification codes — and those ARE available through TenkeyBridge as TaxRate, TaxCode, and TaxAgency.
    • Workaround: Use TaxCode/TaxRate/TaxAgency for Desktop's real tax model instead of AST classifications.
  • query — UNSUPPORTED_BY_DESKTOP: TaxClassification is part of QBO's automated sales tax stack; Desktop uses explicit tax items/codes instead.
  • create — UNSUPPORTED_BY_DESKTOP: TaxClassification cannot be created on Desktop via qbXML.
  • update — UNSUPPORTED_BY_DESKTOP: TaxClassification cannot be updated on Desktop via qbXML.
  • delete — UNSUPPORTED_BY_DESKTOP: TaxClassification cannot be deleted on Desktop via qbXML.

VendorType ​

Verdict: Never · Read: None · Write: None — QBO has no VendorType entity to be compatible with — Desktop's vendor-type list (VendorTypeAdd/Query) has no counterpart in the QBO Accounting API, so TenkeyBridge does not expose it.

Sandbox: —

This entity has no honest QuickBooks Desktop equivalent. Every verb returns UNSUPPORTED_BY_DESKTOP — never a silent empty list.

Query support ​

  • query — UNSUPPORTED_BY_DESKTOP: No QBO VendorType resource to mirror, so no query is exposed.

Not supported ​

  • read — UNSUPPORTED_BY_DESKTOP: The QBO Accounting API has no VendorType resource. Desktop's vendor-type list exists in qbXML, but surfacing it would invent a QBO entity that QBO clients and SDKs do not know.
    • Workaround: Desktop's VendorTypeRef on a vendor is not surfaced on the Vendor entity either. Keep vendor classification in your own system keyed by the TenkeyBridge vendor Id.
  • query — UNSUPPORTED_BY_DESKTOP: No QBO VendorType resource to mirror, so no query is exposed.
  • create — UNSUPPORTED_BY_DESKTOP: No QBO VendorType resource to mirror, so no create is exposed.
  • update — UNSUPPORTED_BY_DESKTOP: No QBO VendorType resource to mirror, so no update is exposed.
  • delete — UNSUPPORTED_BY_DESKTOP: No QBO VendorType resource to mirror, so no delete is exposed.

Batch ​

Verdict: Full · Read: Full · Write: Full — Batch envelope — up to 30 create/update/delete/query operations in one request, executed sequentially with per-item faults.

Sandbox: Yes

This is a gateway-level platform endpoint — not an entity. It cannot be queried or CRUD'd through the entity routes.

http
POST /v3/company/{companyId}/batch
QBO fieldSupportNotes
bIdFullRequired and unique per request; echoed verbatim on each BatchItemResponse slot.
operation (createupdatedelete) + optionsData: void
operation (createupdatedelete)
Query itemsFullFull /query parity: the same query language (AND-joined WHERE with =, !=, <, >, <=, >=, IN, LIKE; ORDERBY; COUNT(*); column lists; STARTPOSITION/MAXRESULTS), iterator-backed deep pages.
per-item faultsFullA failed item returns a Fault slot (with the tkb help block); the rest of the batch still runs.

Everything else listed as full works unchanged.

Not supported ​

  • more than 30 items — BATCH_TOO_MANY_ITEMS: QBO's own per-request cap, cloned. Split into multiple batch calls.
  • optionsData other than void (or void paired with create/delete) — BATCH_UNSUPPORTED_OPTION: Only optionsData "void" on an update item is wired — any other value faults that item loud; the rest of the batch runs.
  • transactional rollback — UNSUPPORTED_BY_DESKTOP: Batch is not a transaction (QBO's isn't either): items succeed or fault independently; earlier writes are never rolled back by a later failure.
  • entity CRUD or /query on Batch itself — UNSUPPORTED_BY_DESKTOP: Batch is a platform endpoint, not an entity — POST /v3/company/{companyId}/batch.

CDC ​

Verdict: Full (read-only) · Read: Full · Write: None — Change Data Capture — one poll returns changed + deleted records across entities.

Sandbox: Yes

This is a gateway-level platform endpoint — not an entity. It cannot be queried or CRUD'd through the entity routes.

http
GET /v3/company/{companyId}/cdc?entities=Invoice,Customer&changedSince=2026-07-01
QBO fieldSupportNotes
entitiesFullComma-separated QBO entity names. Unsupported names return per-entity Fault slots; live entities in the same request still return data.
changedSinceFullISO 8601 timestamp or YYYY-MM-DD, at most 30 days back (QBO's own limit).
deleted-record stubsFullstatus:'Deleted' stubs via Desktop's TxnDeletedQuery/ListDeletedQuery, with TimeDeleted as MetaData.LastUpdatedTime.

Everything else listed as full works unchanged.

Not supported ​

  • changedSince older than 30 days — CDC_INVALID_CHANGED_SINCE: Hard limit, cloned from QBO. Use /query with a MetaData.LastUpdatedTime filter for older data.
  • more than 1,000 objects for one entity — CDC_OVERFLOW: Loud per-entity fault, never silent truncation. Page through /query instead.
  • deactivated list records reported as deletions — UNSUPPORTED_BY_DESKTOP: Desktop only hard-deletes unused list records. Active:false is a change, not a deletion — it surfaces as a changed record.
  • deleted TaxAgency / ExchangeRate / Transfer detection — UNSUPPORTED_BY_DESKTOP: Tax agencies are vendors on Desktop (deletion indistinguishable); exchange rates ride the Currency list; 'Transfer' is not a valid TxnDelType.
  • Budget in the entities list — UNSUPPORTED_BY_DESKTOP: Budget is synthesized from a Desktop report rather than stored as a record, so there is no created/modified timestamp to compare a CDC window against. It returns a per-entity Fault slot; the other entities in the same request still return data.
    • Workaround: Re-read it with SELECT * FROM Budget (or GET /budget/{fiscalYear}-{BudgetType}) whenever you need the current figures.

Entitlements ​

Verdict: Partial · Read: Partial · Write: None — GET /v3/company/{companyId}/entitlements — a static, honest capability document for a QuickBooks Desktop company (no per-file subscription data).

Sandbox: Yes

This is a gateway-level platform endpoint — not an entity. It cannot be queried or CRUD'd through the entity routes.

http
GET /v3/company/{companyId}/entitlements
QBO fieldSupportNotes
Entitlements.platformFullAlways "QuickBooks Desktop" — TenkeyBridge talks to Desktop, never to a QBO subscription.
Entitlements.featuresPartialStatic list of what TenkeyBridge supports on Desktop (accounting API, query, CDC, batch, reports). It is not read from the company file; plan tiers, seats and add-ons (Payroll, Advanced) that QBO reports are not available from Desktop.

Everything else listed as full works unchanged.

Not supported ​

  • QBO subscription tier, seats and add-ons — UNSUPPORTED_BY_DESKTOP: Desktop has no subscription entitlements exposed over qbXML; the response says so explicitly rather than inventing values.

Reports ​

Verdict: Partial · Read: Partial · Write: None — Forty-eight QBO-compatible reports served from QuickBooks Desktop: 24 translated live from Desktop's report engine (summary, detail, aging, balance, sales and sales-tax reports), 22 Desktop extension reports with no QBO twin, and two composites, AccountList (built from Desktop's account list) and CashFlow (built from several queries). CashFlow is derived, not translated: Desktop's SDK has no cash-flow report, so it is computed from Profit and Loss and two Trial Balances and checked against Desktop before it is returned.

Sandbox: Read-only — Served from fixed reports of the sample company (mostly real captures; eleven detail/sales/payables reports are generated from the sample data until recaptured; CashFlow is derived from the fixed sample reports; Budget: fiscal years 2025-2027). Dates, filters and column splits are ignored, the header says so (SandboxStaticReport), and they do not change when you write to the sandbox. The exception is AccountList, which the sandbox emulator answers from the sandbox's own accounts, so it reflects your writes and is not marked SandboxStaticReport.

This is a gateway-level platform endpoint — not an entity. It cannot be queried or CRUD'd through the entity routes.

http
GET /v3/company/{companyId}/reports/ProfitAndLoss?start_date=2026-01-01&end_date=2026-12-31&summarize_column_by=Month
QBO fieldSupportNotes
ProfitAndLoss / BalanceSheet / TrialBalanceFullGeneralSummaryReportQueryRq (ProfitAndLossStandard / BalanceSheetStandard / TrialBalance). Values verbatim from Desktop — no recomputation. BalanceSheet also takes report_date as an as-of.
AgedReceivables / AgedPayablesFullAgingReportQueryRq (ARAgingSummary / APAgingSummary). Desktop's file-configured aging buckets come back as columns, honestly.
CustomerBalance / VendorBalanceFullCustomerBalanceSummary / VendorBalanceSummary. As-of (report_date). Month (and the other period splits) are accepted; Desktop refuses ReportBasis here.
CustomerIncome / VendorExpensesFullIncomeByCustomerSummary / ExpenseByVendorSummary.
CustomerSales / ItemSales (aliases SalesByCustomer / SalesByProduct)FullSalesByCustomerSummary / SalesByItemSummary. ItemSales carries Desktop's extra columns (% of Sales, Avg Price, COGS, Gross Margin).
InventoryValuationSummaryFullInventoryValuationSummary, as-of (report_date), item filter only. Desktop refuses ReportBasis and SummarizeColumnsBy here.
ProfitAndLossDetail / GeneralLedgerPartialGeneralDetailReportQueryRq (ProfitAndLossDetail / GeneralLedger). QBO detail shape: ColType String/Date/Money with MetaData.ColKey (ColKeys best-effort), every transaction row typed Data, accounts as sections. ColData carries value only (Desktop reports carry no TxnID/ListID, so no id).
JournalReport / TransactionListPartialGeneralDetailReportQueryRq Journal / TxnListByDate. ColKeys best-effort. Journal's per-transaction unlabeled subtotals become header-less Sections; TransactionList is a flat Data list. Desktop refuses ReportBasis on both, so accounting_method faults.
InventoryValuationDetail / TransactionListByCustomer / TransactionListByVendorPartialGeneralDetailReportQueryRq InventoryValuationDetail / TxnListByCustomer / TxnListByVendor. ColKeys best-effort. Desktop refuses ReportBasis on all three (3151), so accounting_method faults. InventoryValuationDetail takes the item filter only.
TaxSummaryPartialDesktop's Sales Tax Liability report (GeneralSummaryReportQueryRq SalesTaxLiability): taxable and non-taxable sales and tax collected. accounting_method is honored. Desktop refuses SummarizeColumnsBy here, so summarize_column_by takes Total only.
CustomerBalanceDetail / VendorBalanceDetailPartialGeneralDetailReportQueryRq CustomerBalanceDetail / VendorBalanceDetail, as-of (report_date). Desktop returns one section per customer/vendor with its balance and no transaction rows; passed through as-is.
AgedReceivableDetail / AgedPayableDetailPartialAgingReportQueryRq ARAgingDetail / APAgingDetail, as-of (report_date), with the receivable (customer) or payable (vendor) filter. ColKeys best-effort.
CashFlowPartialDerived (TkbDerivedReport): Desktop's SDK has no cash-flow report, so TenkeyBridge builds the indirect-method statement from Desktop's Profit and Loss and two Trial Balances, classified by account type. Bank accounts plus Undeposited Funds count as cash, as in QBO. Fixed Asset accounts, including accumulated depreciation, are investing (Desktop's own default classification). Self-checking: if the derived net income differs from Desktop's by a cent, or a trial-balance row cannot be matched to an account, the call fails with REPORT_COMPOSE_FAILED (502) instead of answering. Five QuickBooks round trips. Sandbox realms serve it from captured sample inputs (SandboxStaticReport).
AccountListPartialBuilt from Desktop's account list (AccountQueryRq). Desktop has no account detail type, so that column is absent.
Desktop extension reportsFullProfitAndLossByClass, ProfitAndLossByJob, ProfitAndLossPrevYearComp, ProfitAndLossYTDComp, BalanceSheetPrevYearComp, BalanceSheetByClass, PurchaseByVendorSummary, PurchaseByItemSummary, SalesByRepSummary, InventoryStockStatusByItem, OpenInvoices, UnpaidBillsDetail, OpenPOs, SalesByCustomerDetail, SalesByItemDetail, IncomeByCustomerDetail, ExpenseByVendorDetail, PurchaseByVendorDetail, DepositDetail, CheckDetail, BalanceSheetDetail, CollectionsReport. Desktop reports with no QBO twin, served under their Desktop names in the same QBO report JSON.
start_date / end_date / date_macroFullISO dates or QBO date macros (spaced or compact, e.g. Last Month / LastMonth; fiscal-relative on both sides, so macros map 1:1). Omit them and you get fiscal year-to-date, matching QBO — Desktop's own bare default is month-to-date, so TenkeyBridge always sends an explicit period.
report_dateFullThe as-of reports (aging, balance, inventory valuation, and BalanceSheet) are as-of a single date. Omit it and QBO's default (today) is used.
accounting_methodPartialREPORT_UNSUPPORTED_OPTION — Cash/Accrual → ReportBasis on ProfitAndLoss, BalanceSheet, TrialBalance, ProfitAndLossDetail, GeneralLedger, CustomerBalanceDetail, VendorBalanceDetail and the income/expense/sales summaries. Desktop refuses it (3151 or a parse error) on Journal, TransactionList, the balance summaries, InventoryValuationSummary and the aging reports, so those fault.
summarize_column_byPartialREPORT_UNSUPPORTED_OPTION — Total, Month, Week, Days, Quarter, Year, Customers, Vendors, Classes → SummarizeColumnsBy on the period summary reports (Customers is refused on CustomerSales/CustomerIncome and Vendors on VendorExpenses, as Desktop refuses them). CustomerBalance/VendorBalance take the period splits only. Detail, aging and inventory reports take Total only.
customer / vendor / item / classPartialREPORT_UNSUPPORTED_OPTION — Comma-separated Ids → ReportEntityFilter (customer + vendor) / ReportItemFilter / ReportClassFilter. AR reports accept customer only, AP reports vendor only; InventoryValuationSummary accepts item only.
account / account_typePartialREPORT_UNSUPPORTED_OPTION — account is comma-separated account Ids (ReportAccountFilter ListID); account_type is one QBO account type (Bank, AccountsReceivable, OtherCurrentAsset, FixedAsset, OtherAsset, AccountsPayable, CreditCard, OtherCurrentLiability, LongTermLiability, Equity, Income, CostOfGoodsSold, Expense, OtherIncome, OtherExpense) mapped to AccountTypeFilter. Sending both faults. Accepted on the summary reports (except InventoryValuationSummary) and on every detail report except AgedReceivableDetail / AgedPayableDetail. The aging reports refuse both: Desktop's aging account filter does not filter meaningfully.
transaction_typePartialREPORT_UNSUPPORTED_OPTION — Comma-separated QBO transaction types (Invoice, CreditMemo, SalesReceipt, Payment, Deposit, Transfer, Estimate, Bill, VendorCredit, BillPayment, BillPaymentCheck, BillPaymentCreditCard, Check, CreditCardCharge, CreditCardCredit, PurchaseOrder, JournalEntry, InventoryQuantityAdjustment) mapped to ReportTxnTypeFilter. BillPayment fans out to both check and credit-card bill payments. Types with no single Desktop twin (Expense, CashPurchase, RefundReceipt) fault. Accepted on summary, detail and aging reports (not InventoryValuationSummary).
start_moddate / end_moddatePartialREPORT_INVALID_DATE — ISO dates that filter on the transaction last-modified date (ReportModifiedDateRangeFilter). Both are required together, otherwise REPORT_INVALID_DATE. Detail reports only (not AgedReceivableDetail / AgedPayableDetail); summary and aging reports refuse them.
columnsPartialREPORT_UNSUPPORTED_OPTION — Comma-separated QBO ColKeys (tx_date, txn_type, doc_num, txn_num, name, cust_name, vend_name, memo, account_name, split_acc, klass_name, is_cleared, debt_amt, credit_amt, subt_nat_amount, rbal_nat_amount, subt_open_bal, due_date, past_due, po_num, term_name) mapped to IncludeColumn; aliases dedupe and an unknown key faults listing the accepted ones. Detail reports and AgedReceivableDetail / AgedPayableDetail only. When columns is sent, data rows carry the QuickBooks transaction id as id on the txn_type cell (GeneralLedger cost-posting rows have none). If txn_type is not among the requested columns, the id is on the first cell.
Column titlesPartialDesktop's own column titles pass through verbatim, so aging buckets read '> 90' where QBO says '91 and over', and a period column reads 'Jan - Dec 26' where QBO says 'Total'. On summary reports QBO's MetaData.ColKey has no Desktop equivalent and is not invented; detail reports derive it from Desktop's column types (ColKeys best-effort — QBO does not document them).

Everything else listed as full works unchanged.

Not supported ​

  • department / qzurl / adjusted_gain_loss — REPORT_UNSUPPORTED_OPTION: Desktop has no departments (use class); QuickZoom URLs and multicurrency gain/loss adjustment are QBO-only.
  • aging_period / num_periods / aging_method — REPORT_UNSUPPORTED_OPTION: Desktop aging buckets are company-file configuration, not request parameters.
  • summarize_column_by = Employees / ProductsAndServices / Departments / Location — REPORT_UNSUPPORTED_OPTION: qbXML's SummarizeColumnsBy has no per-item, per-employee or per-location split (ItemType splits by item type, not by item).
  • ClassSales / DepartmentSales — REPORT_UNKNOWN: No Desktop SDK report. Use ProfitAndLoss?summarize_column_by=Classes.
  • TransactionListWithSplits — REPORT_UNKNOWN: No Desktop SDK report of that name. Use JournalReport.
  • other QBO report names — REPORT_UNKNOWN: Not shipped. The fault lists the supported reports.

TaxService ​

Verdict: Partial · Read: None · Write: Partial — POST /taxservice/taxcode creates tax rates and the tax code that groups them (Desktop ItemSalesTaxAdd then ItemSalesTaxGroupAdd). The TaxCodeId is a sales tax group, used on a sales form as TxnTaxDetail.TxnTaxCodeRef.

Sandbox: Yes — Creates rates and tax codes. A sales form that uses the tax code is taxed at the sum of its rates.

This is a gateway-level platform endpoint — not an entity. It cannot be queried or CRUD'd through the entity routes.

http
POST /v3/company/{companyId}/taxservice/taxcode
QBO fieldSupportNotes
TaxCodeFullThe tax code name, 1 to 31 characters. Becomes a Desktop sales tax group; a name already in use is refused by QuickBooks.
TaxRateDetails.TaxRateNameFullName of a new rate, 1 to 31 characters (a Desktop sales tax item).
TaxRateDetails.RateValueFullPercentage from 0 to 100.
TaxRateDetails.TaxAgencyIdFullThe tax agency, a Desktop Vendor Id.
TaxRateDetails.TaxApplicableOnPartialSales only. Any other value is UNSUPPORTED_BY_DESKTOP: Desktop sales tax items apply to sales only.
TaxRateDetails.TaxRateIdFullSend it alone to put an existing rate in the new code instead of creating one. Also how to retry after TAX_CODE_PARTIAL.
TaxCodeIdPartialIn the answer. It is a Desktop sales tax group id, not a TaxCode (TAX or NON): GET /taxcode/{id} will not find it. It is what you send as TxnTaxDetail.TxnTaxCodeRef on an invoice or other sales form, which maps to Desktop ItemSalesTaxRef.

Everything else listed as full works unchanged.

Not supported ​

  • read, query, update, delete — UNSUPPORTED_BY_DESKTOP: TaxService is a create-only operation. There is no TaxService object to read back; read the rates as TaxRate.
  • Rates created but the tax code not — TAX_CODE_PARTIAL: The two steps are separate QuickBooks messages. If the tax code is refused after the rates were created (usually a duplicate name), the answer is 502 TAX_CODE_PARTIAL naming every created TaxRateId. Resend with TaxRateDetails [{ TaxRateId }] for those rates and an unused TaxCode name. Resending the original request is refused by QuickBooks as a duplicate name, so no extra rates are created. The endpoint honors requestid: a replay after success returns the tax code read back from QuickBooks, and a replay after this fault does not re-send.

TenkeyBridge is an independent product, not affiliated with, endorsed by, or sponsored by Intuit Inc. QuickBooks, QuickBooks Online, and QuickBooks Desktop are trademarks of Intuit Inc., used only to describe compatibility.