Appearance
Error codes
A developer hitting a TenkeyBridge error should never wonder what happened. Every fault carries three layers of help:
- The QBO-compatible
Fault— the exact shape the QuickBooks Online API uses, so existing QBO SDKs and error handlers keep working unchanged. Branch oncode. - A
tkbblock — TenkeyBridge's enrichment: likely causes, concrete fixes, and a docsUrl deep-linking to that code's section on this page. It sits outsideFault, in a top-level field QBO clients ignore. - A docs link in
Detail— so even if your SDK only surfacesMessage/Detail, the trail to the full story is still in front of you.
The fault envelope
jsonc
{
"Fault": {
"Error": [{
"code": "UNSUPPORTED_BY_DESKTOP",
"Message": "Unsupported by QuickBooks Desktop",
"Detail": "Sparse update of Line is not supported yet for Bill; line-level updates are currently supported on sales forms (Invoice, Estimate, SalesReceipt, CreditMemo) only. Re-create the transaction or update header fields only. See https://docs.tenkeybridge.com/reference/error-codes.html#unsupported_by_desktop",
"element": "Bill.Line"
}],
"type": "ValidationFault"
},
"time": "2026-07-24T00:00:00.000Z",
"tkb": {
"code": "UNSUPPORTED_BY_DESKTOP",
"causes": [
"The field, line type, item type, or operation you sent exists in QuickBooks Online but has no equivalent in QuickBooks Desktop's qbXML API — or the whole entity is a documented Desktop gap."
],
"fixes": [
"Remove or replace the unsupported value; the fault's element and Detail name exactly what triggered it.",
"Check the entity's section on https://docs.tenkeybridge.com/compatibility/ for the full list of supported fields and operations."
],
"docsUrl": "https://docs.tenkeybridge.com/reference/error-codes.html#unsupported_by_desktop"
}
}Fault.Erroris always an array (currently always length 1);codeis the value to branch on;element(when present) names the field that triggered the fault.Fault.typeisValidationFaultfor anything you can fix by changing the request or the company file,SystemFaultfor TenkeyBridge-side failures (HTTP 5xx).tkbis additive and versioned with the codes themselves (ERROR_CODES_VERSION = "2026-07"). Never required for correct handling — branch oncode; readtkbwhen a human needs to know what to do next.
The string codes below are TenkeyBridge's own, part of the public API surface — their meaning won't change under you between versions. Numeric codes carry the same meaning they have in the QuickBooks Online API.
Translation codes
Raised when a request can't be translated honestly to QuickBooks Desktop. These are almost always fixable by changing the request.
UNSUPPORTED_BY_DESKTOP
HTTP 422 · You sent a field, line type, item type, operation, or whole entity that has no QuickBooks Desktop equivalent.
Causes
- The field or operation exists in QuickBooks Online but qbXML (Desktop's API) has no way to express it — a permanent platform gap, not a missing TenkeyBridge feature.
- The entity itself is a documented gap (Attachable, Budget, …) or not yet shipped in TenkeyBridge. Entity-level responses link straight to that entity's section on the compatibility page.
Fixes
- Remove or replace the value — the fault's
elementandDetailname exactly what triggered it. - Check the entity's section on the compatibility page for the full list of supported fields and operations, and the closest supported alternative.
- For Desktop-backed realms, branch on this code and degrade gracefully (skip the feature) rather than retrying — the same request will fail the same way every time.
jsonc
{
"Fault": {
"Error": [{
"code": "UNSUPPORTED_BY_DESKTOP",
"Message": "Unsupported entity",
"Detail": "Item create/update lands in a later TenkeyBridge release. Pull items with read/query for now. See https://docs.tenkeybridge.com/reference/error-codes.html#unsupported_by_desktop",
"element": "Item"
}],
"type": "ValidationFault"
}
}UNSUPPORTED_QUERY
HTTP 400/422 · Your query uses a clause TenkeyBridge can't translate into a reliable Desktop query.
Causes
- Multi-condition
WHERE(AND/OR), anyORDER BY, or aggregate selects (COUNT, …). - A filter on a field Desktop can't index, or
STARTPOSITIONpaging past what Desktop can serve for that query shape.
Fixes
- Simplify to a single supported filter:
Id, a date range onTxnDateorMetaData.LastUpdatedTime,DocNumber, orActive— then filter and sort the rest client-side. - On
Customer, dedup lookups are also supported directly:PrimaryEmailAddr,CompanyName,DisplayName,GivenName,FamilyName(each alone), orGivenName+FamilyNametogether — see the Query support notes on the compatibility page for case-insensitivity and performance caveats.
UNMAPPED_FIELD
HTTP 422 · The payload contains a field TenkeyBridge doesn't recognize for this entity.
Causes
- A typo in a field name, a QBO minor-version field, or a field that belongs to a different entity. Distinct from
UNSUPPORTED_BY_DESKTOP: unmapped means "not a known field at all", unsupported means "known, but Desktop can't do it".
Fixes
- Drop the field or fix the spelling; the entity's field table on the compatibility page lists every accepted field.
UNMAPPED_ACCOUNT_TYPE
HTTP 422 · The AccountType (or AccountSubType) value has no QuickBooks Desktop account-type equivalent.
Fixes
- Use one of the Desktop-mappable account types listed in the Account section of the compatibility page; the fault's
Detailnames the closest supported type when there is one.
MISSING_REQUIRED_DESKTOP_ITEM
HTTP 422 · You referenced something QuickBooks Desktop models as an item in the company file, but the request doesn't point at one that exists.
Causes
- Most commonly a bare-percentage or amount-off discount with no
ItemRef— Desktop discounts are discount items; there is no "anonymous discount".
Fixes
- Create the item in the Desktop company file, then reference it (e.g.
DiscountLineDetail.ItemRef).
Gateway codes
Raised by the TenkeyBridge gateway before or after translation — routing, auth, and the connection to the agent running next to QuickBooks Desktop.
NOT_FOUND
HTTP 404 · The URL doesn't match any TenkeyBridge route, or the entity segment isn't a known QuickBooks entity name at all (typo / unknown string). Known-but-unsupported entities return UNSUPPORTED_BY_DESKTOP instead.
Fixes
- Check the path shape (
/v3/company/{realmId}/{entity}) and the entity spelling against the entity matrix.
AUTHENTICATION_FAILED
HTTP 401 · The Authorization header is missing, isn't a Bearer token, or the access token is invalid or expired.
Causes
- Access tokens live 60 minutes — the most common cause is simply an expired token.
Fixes
- Send
Authorization: Bearer <access_token>. - When the access token expires, exchange your refresh token at
/oauth2/v1/tokens(grant_type=refresh_token) for a fresh pair — and store the new refresh token; refresh tokens rotate on every use.
AUTHORIZATION_FAILED
HTTP 403 · The access token is valid but was issued for a different realm than the one in the URL path.
Fixes
- Use the
realmIdreturned in your OAuth callback together with the tokens minted for it — realm and token travel as a pair. - If you meant to talk to a different company file, run the connect flow again for that realm.
RATE_LIMITED
HTTP 429 · This company (realm) sent more REST requests than the per-minute limit allows.
Every response on this surface — limited or not — carries three headers so you can see your budget before you hit it:
| Header | Meaning |
|---|---|
RateLimit-Limit | Requests allowed per minute for this realm. |
RateLimit-Remaining | Requests left in the current window. |
RateLimit-Reset | Seconds until the window resets. |
A 429 additionally carries:
| Header | Meaning |
|---|---|
Retry-After | Seconds to wait before retrying (an integer, always ≥ 1). |
Causes
- This company (realm) sent more REST requests than the per-minute limit allows.
Fixes
- Wait for the number of seconds in
Retry-After, then retry. - Watch
RateLimit-Remainingon ordinary responses and back off before it reaches 0, rather than polling at the edge of the limit. - Batch or page requests instead of polling in a tight loop; use CDC for change-tracking instead of repeated full queries.
The OAuth token/authorize endpoints and the portal sign-in endpoints enforce their own, separate per-IP limits — see OAuth errors for the slow_down shape returned there. Both carry the same four headers.
QUERY_PARSER_ERROR
HTTP 400 · The query parameter is missing or empty.
Fixes
- Pass a QBO-SQL statement in the
queryparameter, URL-encoded:GET /v3/company/{realmId}/query?query=SELECT%20*%20FROM%20Customer.
BAD_REQUEST
HTTP 400 · The request doesn't form a valid operation.
Causes
- A non-JSON request body, an unsupported
?operation=value, or an update/delete without the record'sId.
Fixes
- The
Detailnames the exact problem; correct the request shape and resend. Updates and deletes need a JSON body carrying the entity'sId(andSyncToken).
AGENT_OFFLINE
HTTP 503 · No TenkeyBridge agent is connected for this realm right now.
Causes
- The Windows machine hosting QuickBooks Desktop is off or asleep, the agent isn't running, or its network path to the gateway is down.
Fixes
- Start the agent on the machine hosting QuickBooks Desktop and retry — requests succeed as soon as it reconnects.
- Nothing is queued: this request was not executed, so it's always safe to retry.
- If the realm polls unattended (e.g. overnight), treat 503 as "come back later", not as a failure.
AGENT_TIMEOUT
HTTP 504 · The agent is connected but QuickBooks Desktop didn't answer in time.
Causes
- A modal dialog open in QuickBooks on the host machine (the #1 culprit), a very large request, or the company file busy with another operation.
Fixes
- Dismiss any open dialog in QuickBooks on the host machine, then retry.
- Break very large queries into pages.
- ⚠️ The request may still have applied after the gateway gave up — re-query before retrying a write to avoid duplicates.
AGENT_EXECUTION_ERROR
HTTP 502 · The agent reached the machine but the request failed at the QuickBooks/COM layer. The raw Windows-side error is kept in the agent and gateway logs (it can contain machine-local detail that doesn't belong in an API response).
Causes
- QuickBooks isn't running, or no company file is open.
- A QuickBooks login or authorization dialog is blocking access.
- The agent and
QBW.exerun at different Windows integrity levels — they must match (run both non-elevated).
Fixes
- On the host machine: open QuickBooks with the company file, run both QuickBooks and the agent non-elevated, and check the agent log for the exact underlying error.
INTERNAL_ERROR
HTTP 500 · An unexpected error inside the TenkeyBridge gateway — not your request and not QuickBooks.
Fixes
- Retry once; if it persists, report it along with the response's
timefield so it can be correlated with the gateway logs.
SUBSCRIPTION_REQUIRED
HTTP 402 · This company's organization has no active TenkeyBridge subscription.
Causes
- The organization's trial ended, or more than one realm is connected on the trial plan — trial accounts are limited to one realm.
- A production subscription's payment lapsed and stayed unpaid past the 7-day grace period.
Fixes
- Subscribe or manage billing for the organization in the TenkeyBridge portal — see the billing guide.
- On a trial, each organization may connect at most one realm — upgrade to a paid plan to connect more.
CDC_INVALID_ENTITIES
HTTP 400 · The entities parameter is missing, empty, or not a comma-separated list of QBO entity names.
Causes
- No
entitiesparameter, an empty one, or a value that isn't a comma-separated list (e.g.entities=Invoice,Customer,Bill).
Fixes
- Pass
entitiesas a comma-separated list of entity names. Unsupported names don't fail the request — they come back as per-entityFaultslots — but the parameter itself must be present and non-empty.
CDC_INVALID_CHANGED_SINCE
HTTP 400 · changedSince is missing, unparseable, or outside the 30-day CDC look-back window.
Causes
changedSinceisn't a parseable ISO 8601 timestamp orYYYY-MM-DDdate.changedSinceis older than the 30-day CDC look-back window — the same limit QuickBooks Online enforces; it also bounds worst-case Desktop query cost.
Fixes
- Pass
changedSinceas an ISO 8601 timestamp (2026-07-01T00:00:00Z) or bare date (2026-07-01) within the last 30 days. - Syncing older data? Do a full walk with
/queryand aMetaData.LastUpdatedTimefilter instead — CDC is for incremental polling.
CDC_OVERFLOW
HTTP 400 · More than 1,000 objects changed for one entity in the requested window.
Causes
- A CDC slot never silently truncates, so once an entity crosses 1,000 changed objects in the window it returns this fault instead of a partial array.
Fixes
- Shorten the
changedSincewindow and poll more frequently. - Or walk this entity with
/queryusing aMetaData.LastUpdatedTimefilter plusSTARTPOSITION/MAXRESULTSpagination — that path has no object cap.
BATCH_INVALID_REQUEST
HTTP 400 · The batch envelope or one of its items is structurally malformed.
Causes
BatchItemRequestis missing, empty, or not an array; a missing or duplicatebId; an item that isn't exactly oneQueryor one entity payload; a missing or unknown operation; or an operation/Idcombination that contradicts itself (create with anId, update/delete without one).
Fixes
- Nothing executed — a malformed envelope never half-runs. The fault
Detailnames the offending item bybIdor index; fix that item's shape and resend the whole batch.
BATCH_TOO_MANY_ITEMS
HTTP 400 · The batch carries more than 30 items.
Causes
- More than 30 items in one batch — the same per-request cap QuickBooks Online enforces.
Fixes
- Split the work into multiple batch calls of at most 30 items each. Items execute sequentially either way, so splitting costs no extra Desktop round trips.
BATCH_UNSUPPORTED_OPTION
HTTP 200 (per-item fault slot — the batch request itself is not a 400) · The item carries an unsupported optionsData value.
Causes
- The item carries
optionsData(e.g."void") — TenkeyBridge has no wiredTxnVoidpath against Desktop yet, and guessing at void semantics would be dishonest.
Fixes
- Drop
optionsDatafrom the item; only this item faulted — the rest of the batch still ran. Track void support on the compatibility page.
REPORT_UNKNOWN
HTTP 422 · The report name in the URL is not one TenkeyBridge serves from Desktop.
Causes
- The report name is not one of the five reports TenkeyBridge serves from QuickBooks Desktop. QuickBooks Online's report catalogue is much larger; the rest —
CashFlow, the detail variants, and the aging-detail reports — are planned, not shipped.
Fixes
- Use one of
ProfitAndLoss,BalanceSheet,TrialBalance,AgedReceivables, orAgedPayables. Names match case-insensitively. - Check the compatibility page for the current report coverage before adding a new report call.
REPORT_UNSUPPORTED_OPTION
HTTP 422 · The request carries a report parameter Desktop cannot honour.
Causes
- The request carries a QuickBooks Online report parameter that Desktop's report engine cannot honour — a column/filter customisation, an aging knob that lives in company-file preferences rather than the request, or
accounting_methodon an aging report (qbXML'sAgingReportQueryRqhas noReportBasiselement).
Fixes
- Drop the parameter and read the standard Desktop layout; the fault's
elementnames exactly which one failed. - For aging buckets, change them in QuickBooks under Edit > Preferences > Reports & Graphs — Desktop returns whatever the company file is configured with, as the report's columns.
- For column or filter customisation, fetch the full report and narrow it client-side.
REPORT_INVALID_DATE
HTTP 400 · A report date or date macro was not usable.
Causes
- A report date was not a real calendar date in
YYYY-MM-DDform,start_dateandend_datewere not supplied together,start_datefell afterend_date, ordate_macrowas not one of QuickBooks Online's date macros.
Fixes
- Send
start_dateandend_datetogether asYYYY-MM-DD, or senddate_macroalone — not a partial pair. - For the aging reports use
report_date(a single as-of date), notstart_date/end_date. - Omit the dates entirely to get the fiscal year-to-date window, which is what the same call returns from QuickBooks Online.
Numeric codes
Numeric codes carry the same meaning they have in the QuickBooks Online API, so existing QBO error-handling logic keeps working unchanged. They come from three places: mapped from QuickBooks Desktop status codes when Desktop itself rejects the request (610, 5010, 6240, 2500, and 2020 from Desktop's 3070); raised by TenkeyBridge's own validation before the request ever reaches Desktop (2010, 2020); and 500 for an internal TenkeyBridge failure.
610 — Object not found
HTTP 400 · No record with the given Id exists in the company file.
Causes
- The record was deleted in QuickBooks, or the
Idbelongs to a different realm. - Desktop Ids differ from QBO Ids for the same logical record — an Id carried over from a QBO integration will never match.
Fixes
- Re-query for the record to get its current Id in this realm.
5010 — Stale object
HTTP 400 · Optimistic-concurrency conflict: the SyncToken you sent is stale (Desktop's EditSequence).
Causes
- A user or another integration modified the record in QuickBooks after you read it.
Fixes
GETthe record again, take the freshSyncToken, and re-apply your change.
jsonc
{
"Fault": {
"Error": [{
"code": "5010",
"Message": "Stale Object Error",
"Detail": "QuickBooks Desktop: The provided edit sequence is out-of-date. See https://docs.tenkeybridge.com/reference/error-codes.html#5010"
}],
"type": "ValidationFault"
}
}6240 — Duplicate name exists
HTTP 400 · A list record with this name already exists.
Causes
- Desktop names (
Customer,Vendor,Item, …) must be unique per list — including inactive records, which is the case that usually surprises QBO-first integrations.
Fixes
- Use the existing record, pick a different name, or rename/reactivate the conflicting record in QuickBooks.
2500 — Invalid reference
HTTP 400 · A *Ref you sent (CustomerRef, ItemRef, AccountRef, …) points at a record that doesn't exist in the company file.
Fixes
- Query for the referenced record first and use its current Id; create it if it doesn't exist yet.
2010 — Invalid field value
HTTP 422 · A field value fails validation — wrong type, an unparseable date or number, or a value outside what Desktop accepts.
Fixes
- The
elementandDetailname the field; correct the value and resend.
2020 — Required parameter missing
HTTP 400/422 · A field the operation requires is missing — either one QBO requires, or one QuickBooks Desktop additionally requires (the Detail says which).
Fixes
- Add the named field and resend. Desktop-only requirements are also flagged per entity on the compatibility page.
500 — System failure
HTTP 500, SystemFault · TenkeyBridge hit an internal integrity error translating Desktop's response — for example a returned record missing its ListID or EditSequence. This is a TenkeyBridge bug, not a problem with your request.
Fixes
- Retry once; if it persists, report it with the response's
timefield.
Desktop status codes
Any QuickBooks Desktop status code without a QBO mapping passes through untouched as the fault's code (message Business Validation Error), so no error is ever swallowed — and the raw QuickBooks message is always preserved in Detail, prefixed QuickBooks Desktop:. For the notoriously cryptic common ones, the tkb block explains what QuickBooks actually means:
| Desktop code | What QuickBooks means | What to do |
|---|---|---|
3000 | The record id is malformed for this entity — Desktop ListIDs/TxnIDs have entity-specific formats, so a QBO id or guessed value won't parse. | Use an Id previously returned by TenkeyBridge for this entity. |
3170 | The record could not be modified — commonly a value the QuickBooks UI would also refuse, or the record is held by another user or open window. | Read the raw message in Detail; close the record's window in QuickBooks, or try single-user mode. |
3175 | The record is locked by another QuickBooks user or an open window. | Close the record's window; retry after the other session finishes. |
3180 | General save error — frequent culprits are sales-tax configuration conflicts or lines referencing accounts/items that changed mid-save. | Read the raw message in Detail; it usually names the list or field involved. |
3250 | The feature the request needs is not enabled in this company file (inventory, sales tax, multicurrency, …). | Enable the feature in QuickBooks preferences, or drop the fields that need it. |
3260 | The QuickBooks user the agent session runs as lacks permission for this action. | Grant the role the permission (Company → Set Up Users and Passwords), or run the agent session as a user with sufficient rights. |
-1 | QuickBooks returned a response TenkeyBridge could not parse a status code from. | Check the agent log on the host machine; report the fault with its Detail. |
Mapped Desktop codes (3100→6240, 3120→610, 3200→5010, 3140→2500, 3070→2020) surface as their numeric QBO codes above.
Admin API errors
The Admin API (/admin/v1/*) speaks its own two-field { "error", "message" } shape too — not the QBO Fault shape above and not the OAuth shape below — see Admin API: Errors for the full table of general-purpose codes (unauthenticated, forbidden, not_found, …). The four codes below are billing-specific (#92); see the billing guide for the plan and grace-period rules behind them.
error | HTTP | What happened | What to do |
|---|---|---|---|
plan_limit_realms | 409 | POST /admin/v1/realms on a trial org that already has its one allowed realm connected. The body additionally carries checkoutPath, the org's checkout endpoint. | Subscribe (POST the checkoutPath) to lift the one-realm trial limit, or delete/reuse the existing realm. |
billing_disabled | 503 | POST .../billing/checkout or .../billing/portal-session called on a gateway with no Stripe configuration. | Nothing to do as a caller — the gateway operator hasn't configured STRIPE_* secrets yet. |
enterprise_plan | 409 | POST .../billing/checkout for an org already on the Enterprise plan. | Enterprise is managed off-platform — contact sales@tenkeybridge.com, not Stripe Checkout. |
no_customer | 409 | POST .../billing/portal-session for an org with no Stripe customer yet. | Start checkout first (POST .../billing/checkout) — a Billing Portal session needs an existing Stripe customer to open. |
OAuth errors
The OAuth endpoints (/oauth2/v1/*) speak RFC 6749, not the QBO Fault shape — errors come back as { "error", "error_description", "error_uri" }. error is the stable value to branch on; error_description says what actually went wrong.
error | HTTP | What happened | What to do |
|---|---|---|---|
invalid_client | 401 | Unknown client_id or wrong client_secret. | Send HTTP Basic auth with client_id as username, client_secret as password. |
invalid_grant (code exchange) | 400 | The authorization code is invalid, expired, already used, or was issued to a different client / redirect_uri. | Codes are single-use, and the token request's redirect_uri must exactly match the authorize request's. Restart the connect flow for a fresh code. |
invalid_grant (refresh) | 400 | The refresh token is invalid, expired, revoked, or already rotated. | Every refresh returns a new refresh token — always store the latest. If the chain is lost, reconnect. |
unsupported_grant_type | 400 | grant_type isn't one TenkeyBridge supports. | Use authorization_code (first exchange) or refresh_token (renewal). |
unsupported_response_type | 400 | /authorize called without response_type=code. | TenkeyBridge implements the authorization-code flow only. |
slow_down | 429 | Too many /authorize + /tokens requests from your IP in the last minute (shared budget across both). | Wait for Retry-After seconds, then retry. Same RateLimit-* + Retry-After headers as RATE_LIMITED. |
See the authentication guide for the full connect flow.