Appearance
IDs, SyncToken & sparse updates
Cross-cutting rules that apply to every live entity. You usually don't need to change application code for these — they're listed so you know what's happening and why behavior matches QuickBooks Online.
IDs
We pass Desktop's ListID / TxnID through as the QBO Id. As long as your code doesn't parse the format of an ID (almost nobody does), references work unchanged across create → read → update → delete.
CompanyInfo is the exception: its Id equals the realm ID (Desktop's company profile has no ListID/TxnID). See the CompanyInfo section.
SyncToken
Maps to Desktop's EditSequence. Optimistic-concurrency conflicts return the same 400 you'd get from QBO when the token is stale — re-fetch and retry.
ReferenceType
QBO's { value, name } shape maps to Desktop's ListID + FullName. Either side of a round-trip keeps both pieces when Desktop returns them.
Updates & sparse updates
Updates (?operation=update, or a POST body with Id) are classified by the body, not just the query param: a POST whose body carries an Id is always treated as an update, whether or not ?operation=update is on the URL — an Id-bearing POST never reaches create, so it can never duplicate the record.
?operation=update — or a body with "sparse": true — with no Id can't be an update, so we fail loud with a 400 instead of guessing.
Desktop has no partial update, so we read-merge-write the full record for you. Your sparse POST behaves like QBO's — with one semantic difference worth knowing: every update behaves as sparse on Desktop, whether or not your request sets "sparse": true. QBO's full update clears any writable field you omit from the body; Desktop's Mod has no "clear what's missing" mode, so omitted fields are always preserved, never cleared — even on a full-body update with no sparse flag. If your client relies on QBO's full-update semantics to blank out a field by omitting it, that won't happen here; set the field explicitly instead.
Line-level updates on sales forms
On Invoice, Estimate, SalesReceipt, and CreditMemo, an update that carries Line replaces the entire line table — the same contract as QBO, where the Line array on an update is always the complete new set. Two Desktop-flavored consequences to know:
- Every line in the update is re-created on Desktop, so line
Ids change on every line-level update. Don't cache sales-form line Ids across writes, and expect links that point at a specific line (e.g. a partially-invoiced estimate's line links) to be severed by a line-level update. - Any existing line you omit from
Lineis deleted. To leave the line table completely untouched, omitLinefrom the update body entirely — header-only updates never touch lines. An emptyLine: []is rejected (2020) rather than deleting every line on a form that requires at least one.
CustomerMemo and TxnTaxDetail.TxnTaxCodeRef are also updatable on these four entities. Line-level updates on other transaction entities (Bill, JournalEntry, …) still fail loud with UNSUPPORTED_BY_DESKTOP.
Deletes
POST /{entity}?operation=delete works for transactions (Invoice, SalesReceipt, Bill, Payment, TimeActivity, Estimate) and returns QBO's {"status": "Deleted"} shape.
One honesty note: Desktop's delete takes no SyncToken, so a stale token can't be rejected the way QBO would. Name-list entities (Customer, Vendor, Account, Item) are never deleted — matching QBO, deactivate them with a sparse Active: false update.
Query support
TenkeyBridge compiles a deliberate subset of QBO's SQL-like query language:
SELECT * FROM Entity(column lists and aggregates are rejected)- Single-condition
WHEREon indexed fields (Id, date ranges onTxnDate/MetaData.LastUpdatedTime,DocNumber,Activewhere applicable) - Customer dedup lookups:
PrimaryEmailAddr,CompanyName,DisplayName,GivenName,FamilyName(alone), orGivenName+FamilyNametogether STARTPOSITION/MAXRESULTSpagination within documented caps
Anything outside that subset returns UNSUPPORTED_QUERY rather than a silent mis-parse. Per-entity exceptions (e.g. CompanyInfo rejects all WHERE/pagination) are listed under each entity's Query support section on the compatibility page.
Errors
We return QBO-style Fault JSON, so existing error handling keeps working. Anything Desktop genuinely can't do — or that TenkeyBridge has not shipped yet — returns a documented code. See Error codes.
Gap entities (no Desktop equivalent) and planned entities (not yet built) both return UNSUPPORTED_BY_DESKTOP with a message that links to the entity's compat section. Genuinely unknown entity names still return NOT_FOUND.