Skip to content

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.

ReportqbXML requestDesktop report type
ProfitAndLossGeneralSummaryReportQueryRqProfitAndLossStandard
BalanceSheetGeneralSummaryReportQueryRqBalanceSheetStandard
TrialBalanceGeneralSummaryReportQueryRqTrialBalance
AgedReceivablesAgingReportQueryRqARAgingSummary
AgedPayablesAgingReportQueryRqAPAgingSummary
CustomerBalanceGeneralSummaryReportQueryRqCustomerBalanceSummary
VendorBalanceGeneralSummaryReportQueryRqVendorBalanceSummary
CustomerIncomeGeneralSummaryReportQueryRqIncomeByCustomerSummary
VendorExpensesGeneralSummaryReportQueryRqExpenseByVendorSummary
CustomerSales (SalesByCustomer)GeneralSummaryReportQueryRqSalesByCustomerSummary
ItemSales (SalesByProduct)GeneralSummaryReportQueryRqSalesByItemSummary
InventoryValuationSummaryGeneralSummaryReportQueryRqInventoryValuationSummary
ProfitAndLossDetailGeneralDetailReportQueryRqProfitAndLossDetail
GeneralLedgerGeneralDetailReportQueryRqGeneralLedger
JournalReportGeneralDetailReportQueryRqJournal
TransactionListGeneralDetailReportQueryRqTxnListByDate
CustomerBalanceDetailGeneralDetailReportQueryRqCustomerBalanceDetail
VendorBalanceDetailGeneralDetailReportQueryRqVendorBalanceDetail
AgedReceivableDetailAgingReportQueryRqARAgingDetail
AgedPayableDetailAgingReportQueryRqAPAgingDetail

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 ​

ParameterReportsNotes
start_date, end_dateperiod reports (ProfitAndLoss, BalanceSheet, TrialBalance, CustomerIncome, VendorExpenses, CustomerSales, ItemSales, ProfitAndLossDetail, GeneralLedger, JournalReport, TransactionList)ISO YYYY-MM-DD. Must be sent together.
report_dateas-of reports (both aging pairs, CustomerBalance, VendorBalance, their detail reports, InventoryValuationSummary) and BalanceSheetSingle as-of date. On BalanceSheet it is an alternative to the range, not an addition to it.
date_macroallQBO 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_methodProfitAndLoss, BalanceSheet, TrialBalance, ProfitAndLossDetail, GeneralLedger, CustomerBalanceDetail, VendorBalanceDetail, CustomerIncome, VendorExpenses, CustomerSales, ItemSalesCash or Accrual → Desktop's ReportBasis. Desktop refuses ReportBasis on JournalReport, TransactionList, CustomerBalance, VendorBalance, InventoryValuationSummary and the aging reports, so those fault.
summarize_column_byperiod summary reports, plus Month/Week/Days/Quarter/Year on CustomerBalance and VendorBalanceTotal (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, vendorall except InventoryValuationSummary; aging reports take only their own (customer on AR, vendor on AP)One or more comma-separated Ids → ReportEntityFilter.
itemperiod, balance and inventory reports (not the aging reports)Comma-separated Ids → ReportItemFilter.
classperiod reports (not the aging, balance or inventory reports)Comma-separated Ids → ReportClassFilter.
minorversionallAccepted 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 a ColType of String, Date or Money and a MetaData entry { "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_name on 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 in Summary.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 section Header.
  • Every transaction row is "type": "Data", even at the top level of a flat report such as TransactionList (whose period-naming wrapper row is dropped).
  • JournalReport closes each transaction with an unlabeled subtotal; each transaction's rows become a header-less Section whose Summary holds its debit and credit totals.
  • An empty aging bucket still arrives as a section with no rows and a "Total …" summary.
  • ColData carries value only. QBO also gives an id for names, accounts and transactions; Desktop's report rows carry no TxnID or ListID, so none is invented.
  • Report headers echo Desktop's ReportBasis except 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 Section has a Header (the section's name), nested Rows, and a Summary (its subtotal line).
  • A summary-only Section — Summary with no Header and no Rows — is a computed line like Gross Profit, Net Income, or a report's grand TOTAL. QBO emits these the same way.
  • A data row carries ColData only. Rows at the top level have no type; 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 with Columns.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 says 127673.01.
  • Column titles are Desktop's, verbatim. Desktop's aging buckets read Current, 1 - 30, 31 - 60, 61 - 90, > 90, TOTAL where QBO writes 91 and over and Total; a period column reads Jan - Dec 26 where QBO writes Total. 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's MetaData.ColKey has no Desktop equivalent and is not invented.
  • Aging buckets are company-file configuration, not request parameters. aging_period, num_periods, and aging_method all fault with REPORT_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_method is rejected on the aging reports. qbXML's AgingReportQueryRq has no ReportBasis element, so there is no honest way to produce a cash-basis aging report.
  • Key on section header text, not on group. group is 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 no group at all, exactly as QBO omits it for sections it does not recognise.
  • Header.Option carries no AccountingStandard. 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 is class), qzurl, and adjusted_gain_loss fault loudly, as do Employees / ProductsAndServices / Departments / Location column splits — qbXML's SummarizeColumnsBy has no per-item, per-employee or per-location split.
  • Detail columns are Desktop's. CustomerBalanceDetail and VendorBalanceDetail come 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_method and summarize_column_by are accepted but ignored, so the sandbox's Header reports what it actually served (SummarizeColumnsBy is Total, ReportBasis is the capture's, no request echo) and carries Option SandboxStaticReport: "true".
  • Report calls are not batchable. A Reports item 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-ProfitAndLoss or 2027-BalanceSheet. Desktop budgets carry no ListID or TxnID, so TenkeyBridge mints a deterministic id you can construct yourself. SyncToken is always "0".
  • SELECT * probes a window, it does not enumerate. BudgetSummaryReportQueryRq requires a FiscalYear, so there is no "list budgets" call. A SELECT * FROM Budget asks 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/BudgetDel in qbXML, so create, update, and delete fault with UNSUPPORTED_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 ​

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.