Appearance
Reports
GET /v3/company/{realmId}/reports/{reportName} serves 20 QuickBooks Online–compatible reports straight out of QuickBooks Desktop's own report engine. One call is one qbXML round trip; the response is QBO's Header / Columns / Rows shape, so existing QBO report code keeps working.
Report names match case-insensitively, and the response echoes QBO's exact casing back in Header.ReportName. SalesByCustomer and SalesByProduct (QBO's UI names) are accepted as aliases of CustomerSales and ItemSales.
Supported reports
Every report below has been run against QuickBooks Enterprise 24.
| Report | qbXML request | Desktop report type |
|---|---|---|
ProfitAndLoss | GeneralSummaryReportQueryRq | ProfitAndLossStandard |
BalanceSheet | GeneralSummaryReportQueryRq | BalanceSheetStandard |
TrialBalance | GeneralSummaryReportQueryRq | TrialBalance |
AgedReceivables | AgingReportQueryRq | ARAgingSummary |
AgedPayables | AgingReportQueryRq | APAgingSummary |
CustomerBalance | GeneralSummaryReportQueryRq | CustomerBalanceSummary |
VendorBalance | GeneralSummaryReportQueryRq | VendorBalanceSummary |
CustomerIncome | GeneralSummaryReportQueryRq | IncomeByCustomerSummary |
VendorExpenses | GeneralSummaryReportQueryRq | ExpenseByVendorSummary |
CustomerSales (SalesByCustomer) | GeneralSummaryReportQueryRq | SalesByCustomerSummary |
ItemSales (SalesByProduct) | GeneralSummaryReportQueryRq | SalesByItemSummary |
InventoryValuationSummary | GeneralSummaryReportQueryRq | InventoryValuationSummary |
ProfitAndLossDetail | GeneralDetailReportQueryRq | ProfitAndLossDetail |
GeneralLedger | GeneralDetailReportQueryRq | GeneralLedger |
JournalReport | GeneralDetailReportQueryRq | Journal |
TransactionList | GeneralDetailReportQueryRq | TxnListByDate |
CustomerBalanceDetail | GeneralDetailReportQueryRq | CustomerBalanceDetail |
VendorBalanceDetail | GeneralDetailReportQueryRq | VendorBalanceDetail |
AgedReceivableDetail | AgingReportQueryRq | ARAgingDetail |
AgedPayableDetail | AgingReportQueryRq | APAgingDetail |
Any other QBO report name returns REPORT_UNKNOWN with the supported names in the fault detail. Notably, CashFlow is not available: QuickBooks Desktop's SDK has no cash-flow report (qbXML has no statement-of-cash-flows report type), so there is nothing to translate. BalanceSheetDetail is not a QBO report either.
Parameters
| Parameter | Reports | Notes |
|---|---|---|
start_date, end_date | period reports (ProfitAndLoss, BalanceSheet, TrialBalance, CustomerIncome, VendorExpenses, CustomerSales, ItemSales, ProfitAndLossDetail, GeneralLedger, JournalReport, TransactionList) | ISO YYYY-MM-DD. Must be sent together. |
report_date | as-of reports (both aging pairs, CustomerBalance, VendorBalance, their detail reports, InventoryValuationSummary) and BalanceSheet | Single as-of date. On BalanceSheet it is an alternative to the range, not an addition to it. |
date_macro | all | QBO date macros, spaced or compact, case-insensitive (This Fiscal Year-to-date, Last Month, LastMonth, ThisYearToDate, …; "Fiscal" is optional). Fiscal-relative on both sides, so they map 1:1. |
accounting_method | ProfitAndLoss, BalanceSheet, TrialBalance, ProfitAndLossDetail, GeneralLedger, CustomerBalanceDetail, VendorBalanceDetail, CustomerIncome, VendorExpenses, CustomerSales, ItemSales | Cash or Accrual → Desktop's ReportBasis. Desktop refuses ReportBasis on JournalReport, TransactionList, CustomerBalance, VendorBalance, InventoryValuationSummary and the aging reports, so those fault. |
summarize_column_by | period summary reports, plus Month/Week/Days/Quarter/Year on CustomerBalance and VendorBalance | Total (default), Month, Week, Days, Quarter, Year, Customers, Vendors, Classes → qbXML SummarizeColumnsBy (Month, Week, Day, Quarter, Year, Customer, Vendor, Class). Desktop refuses Customers on CustomerSales and CustomerIncome and Vendors on VendorExpenses. Detail, aging and inventory valuation reports accept only Total. |
customer, vendor | all except InventoryValuationSummary; aging reports take only their own (customer on AR, vendor on AP) | One or more comma-separated Ids → ReportEntityFilter. |
item | period, balance and inventory reports (not the aging reports) | Comma-separated Ids → ReportItemFilter. |
class | period reports (not the aging, balance or inventory reports) | Comma-separated Ids → ReportClassFilter. |
minorversion | all | Accepted and ignored, as everywhere else in TenkeyBridge. |
Request elements are sent in the order Desktop requires: period, filters, ReportAgingAsOf, SummarizeColumnsBy, then ReportBasis.
If you send no dates at all you get fiscal year-to-date — the same window QuickBooks Online returns for a dateless call. This is deliberate: Desktop's own bare default is month-to-date, so TenkeyBridge always sends an explicit period rather than let the two platforms silently disagree about what "no dates" means. An undated as-of report defaults to today, again matching QBO.
Detail reports
The detail reports (ProfitAndLossDetail, GeneralLedger, JournalReport, TransactionList, CustomerBalanceDetail, VendorBalanceDetail, AgedReceivableDetail, AgedPayableDetail) list transactions row by row. Their shape follows QBO's:
Columns.Column[n]has aColTypeofString,DateorMoneyand aMetaDataentry{ "Name": "ColKey", "Value": … }. ColKeys are best-effort: QBO does not document its column keys, so they are mapped from Desktop's exact column types —Date→tx_date,TxnType→txn_type,RefNumber→doc_num,Name→name(cust_name/vend_nameon the customer and vendor reports),Memo→memo,Account→account_name,SplitAccount→split_acc,Debit→debt_amt,Credit→credit_amt,Amount→subt_nat_amount,RunningBalance→rbal_nat_amount,OpenBalance→subt_open_bal,DueDate→due_date,Aging→past_due,Class→klass_name,ClearedStatus→is_cleared,PONumber→po_num,Terms→term_name,TxnNumber→txn_num. A column Desktop adds that is not in this table keeps a snake-cased version of its own title as its key.- Desktop's leading blank column is folded away. Section names (an account, a customer, an aging bucket) appear in
Header.ColData[0]and "Total …" captions inSummary.ColData[0], as in QBO. A customer or account section opens with a Desktop row carrying the name and its opening balance; those cells become the sectionHeader. - Every transaction row is
"type": "Data", even at the top level of a flat report such asTransactionList(whose period-naming wrapper row is dropped). JournalReportcloses each transaction with an unlabeled subtotal; each transaction's rows become a header-lessSectionwhoseSummaryholds its debit and credit totals.- An empty aging bucket still arrives as a section with no rows and a "Total …" summary.
ColDatacarriesvalueonly. QBO also gives anidfor names, accounts and transactions; Desktop's report rows carry noTxnIDorListID, so none is invented.- Report headers echo Desktop's
ReportBasisexcept on the aging reports (QBO's own aging headers carry none).
Example
bash
curl -s -H "Authorization: Bearer $TOKEN" \
"https://api.tenkeybridge.com/v3/company/$REALM/reports/ProfitAndLoss?start_date=2026-01-01&end_date=2026-12-31"json
{
"Header": {
"Time": "2026-07-30T12:00:00-07:00",
"ReportName": "ProfitAndLoss",
"ReportBasis": "Accrual",
"StartPeriod": "2026-01-01",
"EndPeriod": "2026-12-31",
"SummarizeColumnsBy": "Total",
"Currency": "USD",
"Option": [{ "Name": "NoReportData", "Value": "false" }]
},
"Columns": {
"Column": [
{ "ColTitle": "", "ColType": "Account" },
{ "ColTitle": "Jan - Dec 26", "ColType": "Money" }
]
},
"Rows": {
"Row": [
{
"Header": { "ColData": [{ "value": "Income" }, { "value": "" }] },
"Rows": {
"Row": [
{
"Header": { "ColData": [{ "value": "40100 · Construction Income" }, { "value": "" }] },
"Rows": {
"Row": [
{ "ColData": [{ "value": "40110 · Design Income" }, { "value": "36669.25" }], "type": "Data" }
]
},
"Summary": { "ColData": [{ "value": "Total 40100 · Construction Income" }, { "value": "418731.65" }] },
"type": "Section"
}
]
},
"Summary": { "ColData": [{ "value": "Total Income" }, { "value": "425136.05" }] },
"type": "Section",
"group": "Income"
},
{ "Summary": { "ColData": [{ "value": "Net Income" }, { "value": "127673.01" }] }, "type": "Section", "group": "NetIncome" }
]
}
}(Trimmed — the real Rock Castle response is 8 root rows.)
Reading the row tree
qbXML hands back a flat stream of rows; QBO wants a tree. TenkeyBridge rebuilds it:
- A
Sectionhas aHeader(the section's name), nestedRows, and aSummary(its subtotal line). - A summary-only
Section—Summarywith noHeaderand noRows— is a computed line likeGross Profit,Net Income, or a report's grandTOTAL. QBO emits these the same way. - A data row carries
ColDataonly. Rows at the top level have notype; nested rows carry"type": "Data". That asymmetry is QBO's, and it is reproduced verbatim. - Cells are always padded to the full column count, so
ColData[n]always lines up withColumns.Column[n]. A cell Desktop had no value for is{"value": ""}.
Honest boundaries
- Values are Desktop's, verbatim. No rounding, no recomputation, no client-side arithmetic. If Desktop says
127673.01, the API says127673.01. - Column titles are Desktop's, verbatim. Desktop's aging buckets read
Current,1 - 30,31 - 60,61 - 90,> 90,TOTALwhere QBO writes91 and overandTotal; a period column readsJan - Dec 26where QBO writesTotal. Aging buckets must stay Desktop's — a company file can be configured with entirely different buckets, so printing QBO's labels over them would be a lie — and the same rule is applied to every column for consistency. On the summary reports QBO'sMetaData.ColKeyhas no Desktop equivalent and is not invented. - Aging buckets are company-file configuration, not request parameters.
aging_period,num_periods, andaging_methodall fault withREPORT_UNSUPPORTED_OPTION. Change them in QuickBooks under Edit > Preferences > Reports & Graphs; whatever the file is configured with comes back as the report's columns. accounting_methodis rejected on the aging reports. qbXML'sAgingReportQueryRqhas noReportBasiselement, so there is no honest way to produce a cash-basis aging report.- Key on section header text, not on
group.groupis emitted only where the section name confidently maps to a QBO group (Income,COGS,Expenses,TotalAssets,GrandTotal, …). Desktop-specific sections — anything named after a numbered account, for instance — carry nogroupat all, exactly as QBO omits it for sections it does not recognise. Header.Optioncarries noAccountingStandard. QBO reports GAAP; Desktop never states an accounting standard, so it is omitted rather than assumed.- Column selection and a few filters are unsupported.
columns,department(Desktop's equivalent isclass),qzurl, andadjusted_gain_lossfault loudly, as doEmployees/ProductsAndServices/Departments/Locationcolumn splits — qbXML'sSummarizeColumnsByhas no per-item, per-employee or per-location split. - Detail columns are Desktop's.
CustomerBalanceDetailandVendorBalanceDetailcome back from Desktop with one section per customer or vendor and its balance, and no individual transaction rows; TenkeyBridge passes that through rather than fill it in. - Sandbox reports are static. In a developer sandbox every report replays a fixed report of the ExampleCo sample company. Most are real QuickBooks captures; eleven (GeneralLedger, JournalReport, TransactionList, ProfitAndLossDetail, ItemSales, InventoryValuationSummary, CustomerIncome, VendorExpenses, VendorBalance, VendorBalanceDetail, AgedPayableDetail) are generated from the sample data until they can be recaptured. Dates, filters,
accounting_methodandsummarize_column_byare accepted but ignored, so the sandbox'sHeaderreports what it actually served (SummarizeColumnsByisTotal,ReportBasisis the capture's, no request echo) and carriesOptionSandboxStaticReport: "true". - Report calls are not batchable. A
Reportsitem inside a batch envelope faults per-item pointing back at this endpoint — reports are a platform surface, not an entity, so the batch envelope has nothing to execute.
Budget is an entity, not a report
QuickBooks Desktop's budget figures come out of a report message (BudgetSummaryReportQuery), but TenkeyBridge serves them as QBO's read-only Budget entity — not through this endpoint — because that is where QBO-compatible clients look for them.
bash
# Every budget in the probed window
curl -s "$TKB/v3/company/$REALM/query?query=SELECT%20*%20FROM%20Budget" \
-H "Authorization: Bearer $TOKEN"
# One budget, by its synthetic id
curl -s "$TKB/v3/company/$REALM/budget/2026-ProfitAndLoss" \
-H "Authorization: Bearer $TOKEN"json
{
"Budget": {
"Id": "2026-ProfitAndLoss",
"SyncToken": "0",
"Name": "FY2026 Profit and Loss Budget",
"StartDate": "2026-01-01",
"EndDate": "2026-12-31",
"BudgetType": "ProfitAndLoss",
"BudgetEntryType": "Monthly",
"Active": true,
"BudgetDetail": [
{
"BudgetDate": "2026-01-01",
"Amount": 12000.0,
"AccountRef": { "value": "80000030-1797300000", "name": "Consulting Income" }
}
],
"sparse": false,
"domain": "QBO"
}
}Three things about it are unlike every other entity, and they follow from qbXML having no Budget record at all:
- Ids are synthetic:
{fiscalYear}-{BudgetType}, e.g.2026-ProfitAndLossor2027-BalanceSheet. Desktop budgets carry noListIDorTxnID, so TenkeyBridge mints a deterministic id you can construct yourself.SyncTokenis always"0". SELECT *probes a window, it does not enumerate.BudgetSummaryReportQueryRqrequires aFiscalYear, so there is no "list budgets" call. ASELECT * FROM Budgetasks Desktop for the current calendar year, the year before, and the year after × 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 still reachable by id. Because the probe is speculative, the query never fails on an individual year: a company with no budgets gets an empty list, the same answer QuickBooks Online gives.GET /budget/{id}asks about one budget, so it does report that budget's status — a Desktop error surfaces as a fault, and a year with no budget is a 404.- Reads only, forever. There is no
BudgetAdd/BudgetMod/BudgetDelin qbXML, so create, update, and delete fault withUNSUPPORTED_BY_DESKTOP. QBO's own Budget entity is read-only too, so nothing is lost.
WHERE Id = '…' is the only filter (it compiles to the same single report request as GET /budget/{id}); anything else faults with UNSUPPORTED_QUERY. Class and customer budget breakdowns, and Desktop's budget-vs-actual report types, are documented boundaries — see the compatibility entry.
See also
- Compatibility matrix — Reports
- Compatibility matrix — Budget
REPORT_UNKNOWN·REPORT_UNSUPPORTED_OPTION·REPORT_INVALID_DATE- Batch operations — and why report calls stay out of the envelope

