Debugging QuickBooks Online API: Missing Sales Tax Category Due to MinorVersion Parameter
By manhnv, at: March 5, 2025, 2:01 p.m.
Estimated Reading Time: __READING_TIME__ minutes


Debugging QuickBooks Online API: Missing Sales Tax Category Due to MinorVersion Parameter
Introduction
While working with the QuickBooks Online (QBO) API, I encountered an inconsistency between UAT and Production environments when retrieving invoice and item data. The issue? The TaxClassificationRef
field, which helps determine an item’s sales tax category, was missing in production but present in UAT.
After debugging, I discovered the root cause was the minorversion
parameter in the API request. This blog post documents my findings, the debugging process, and the final solution.
The Problem: Missing TaxClassificationRef in Production
I ran the following Python QuickBooks queries for Invoice
and Item
in both UAT and Production:
Production API Response (Missing TaxClassificationRef)
Invoice Query Response
Invoice.get(9999, qb=qb).to_dict()
Relevant Output Snippet
{'Id': '9999',
'SyncToken': '1',
'sparse': False,
'domain': 'QBO',
'Deposit': 0,
'Balance': 455.73,
'AllowIPNPayment': False,
'AllowOnlineCreditCardPayment': True,
'AllowOnlineACHPayment': True,
'DocNumber': '1927',
'PrivateNote': '',
'DueDate': '2025-04-04',
'ShipDate': '',
'TrackingNum': '',
'TotalAmt': 455.73,
'TxnDate': '2025-03-05',
'ApplyTaxAfterDiscount': False,
'PrintStatus': 'NeedToPrint',
'EmailStatus': 'NotSet',
'ExchangeRate': 1,
'GlobalTaxCalculation': 'TaxExcluded',
'InvoiceLink': '',
'HomeBalance': 0,
'HomeTotalAmt': 0,
'FreeFormAddress': False,
'EInvoiceStatus': None,
'BillAddr': {'Id': '715',
'Line1': 'Glinteco',
'Line2': '132 My Street',
'Line3': 'Kingston, New York 12401 US',
'Line4': '',
'Line5': '',
'City': '',
'CountrySubDivisionCode': '',
'Country': '',
'PostalCode': '',
'Lat': '',
'Long': '',
'Note': ''},
'ShipAddr': {'Id': '716',
'Line1': 'Glinteco',
'Line2': '132 My Street',
'Line3': 'Kingston, New York 12401 US',
'Line4': '',
'Line5': '',
'City': '',
'CountrySubDivisionCode': '',
'Country': '',
'PostalCode': '',
'Lat': '',
'Long': '',
'Note': ''},
'BillEmail': {'Address': '[email protected]'},
'BillEmailCc': None,
'BillEmailBcc': None,
'CustomerRef': {'value': '145', 'name': 'Glinteco', 'type': ''},
'CurrencyRef': {'value': 'USD', 'name': 'United States Dollar', 'type': ''},
'CustomerMemo': None,
'DepartmentRef': None,
'TxnTaxDetail': {'TotalTax': 34.73,
'TxnTaxCodeRef': {'value': '3', 'name': '', 'type': ''},
'TaxLine': [{'Amount': 26.31,
'DetailType': 'TaxLineDetail',
'TaxLineDetail': {'PercentBased': True,
'TaxPercent': 6.25,
'NetAmountTaxable': 0,
'TaxRateRef': {'value': '3', 'name': '', 'type': ''}}},
{'Amount': 0,
'DetailType': 'TaxLineDetail',
'TaxLineDetail': {'PercentBased': True,
'TaxPercent': 0,
'NetAmountTaxable': 0,
'TaxRateRef': {'value': '4', 'name': '', 'type': ''}}},
{'Amount': 5.26,
'DetailType': 'TaxLineDetail',
'TaxLineDetail': {'PercentBased': True,
'TaxPercent': 1.25,
'NetAmountTaxable': 0,
'TaxRateRef': {'value': '5', 'name': '', 'type': ''}}},
{'Amount': 3.16,
'DetailType': 'TaxLineDetail',
'TaxLineDetail': {'PercentBased': True,
'TaxPercent': 0.75,
'NetAmountTaxable': 0,
'TaxRateRef': {'value': '6', 'name': '', 'type': ''}}}]},
'DeliveryInfo': None,
'RecurDataRef': None,
'TaxExemptionRef': None,
'MetaData': {'CreateTime': '2025-03-04T20:02:58-08:00',
'LastUpdatedTime': '2025-03-04T20:03:02-08:00'},
'CustomField': [{'DefinitionId': '1',
'Type': 'StringType',
'Name': 'PO Number',
'StringValue': ''}],
'Line': [{'Id': '1',
'LineNum': 1,
'Description': None,
'Amount': 421.0,
'DetailType': 'SalesItemLineDetail',
'LinkedTxn': [],
'CustomField': [],
'SalesItemLineDetail': {'UnitPrice': 421,
'Qty': 1,
'ServiceDate': '9999-12-31',
'TaxInclusiveAmt': 0,
'MarkupInfo': None,
'ItemRef': {'value': '1010000001',
'name': 'Services:glinteco',
'type': ''},
'ClassRef': None,
'TaxCodeRef': {'value': 'TAX', 'name': '', 'type': ''},
'PriceLevelRef': None}},
{'Id': None,
'LineNum': 0,
'Description': None,
'Amount': 421.0,
'DetailType': 'SubTotalLineDetail',
'LinkedTxn': [],
'CustomField': [],
'SubtotalLineDetail': None,
'SubTotalLineDetail': {}}],
'LinkedTxn': [],
'AllowOnlinePayment': False,
'SalesTermRef': {'value': '3', 'name': 'Net 30', 'type': ''}}
Item Query Response
Item.get(20812, qb=qb).to_dict()
Relevant Output Snippet
{'Id': '20812',
'SyncToken': '0',
'sparse': False,
'domain': 'QBO',
'Name': 'glinteco',
'Description': '',
'Active': True,
'SubItem': True,
'FullyQualifiedName': 'Services:glinteco',
'Taxable': True,
'SalesTaxIncluded': None,
'UnitPrice': 0,
'Type': 'Service',
'Level': 1,
'PurchaseDesc': None,
'PurchaseTaxIncluded': None,
'PurchaseCost': 0,
'TrackQtyOnHand': False,
'QtyOnHand': None,
'InvStartDate': None,
'AssetAccountRef': None,
'ExpenseAccountRef': None,
'IncomeAccountRef': {'value': '1', 'name': 'Services', 'type': ''},
'SalesTaxCodeRef': None,
'ParentRef': {'value': '15', 'name': 'Services', 'type': ''},
'PurchaseTaxCodeRef': None,
'AbatementRate': None,
'ReverseChargeRate': None,
'ServiceType': None,
'ItemCategoryType': None,
'Sku': None,
'MetaData': {'CreateTime': '2025-03-04T20:02:29-08:00',
'LastUpdatedTime': '2025-03-04T20:02:29-08:00'}}
UAT API Response (Contains TaxClassificationRef)
In UAT, the same queries returned a complete dataset, including TaxClassificationRef
:
{'Id': '152',
'SyncToken': '0',
'sparse': False,
'domain': 'QBO',
'Deposit': 0,
'Balance': 35.0,
'AllowIPNPayment': False,
'AllowOnlineCreditCardPayment': False,
'AllowOnlineACHPayment': False,
'DocNumber': '1045',
'PrivateNote': '',
'DueDate': '2025-04-04',
'ShipDate': '',
'TrackingNum': '',
'TotalAmt': 35.0,
'TxnDate': '2025-03-05',
'ApplyTaxAfterDiscount': False,
'PrintStatus': 'NeedToPrint',
'EmailStatus': 'NotSet',
'ExchangeRate': 1,
'GlobalTaxCalculation': 'TaxExcluded',
'InvoiceLink': 'https://developer.intuit.com/comingSoonview/scs-v1-12312?locale=en_US&cta=v3invoicelink',
'HomeBalance': 0,
'HomeTotalAmt': 0,
'FreeFormAddress': True,
'EInvoiceStatus': None,
'BillAddr': {'Id': '2',
'Line1': '4581 Finch St.',
'Line2': '',
'Line3': '',
'Line4': '',
'Line5': '',
'City': 'Bayshore',
'CountrySubDivisionCode': 'CA',
'Country': '',
'PostalCode': '94326',
'Lat': 'INVALID',
'Long': 'INVALID',
'Note': ''},
'ShipAddr': {'Id': '2',
'Line1': '4581 Finch St.',
'Line2': '',
'Line3': '',
'Line4': '',
'Line5': '',
'City': 'Bayshore',
'CountrySubDivisionCode': 'CA',
'Country': '',
'PostalCode': '94326',
'Lat': 'INVALID',
'Long': 'INVALID',
'Note': ''},
'BillEmail': {'Address': '[email protected]'},
'BillEmailCc': None,
'BillEmailBcc': None,
'CustomerRef': {'value': '1', 'name': "Amy's Bird Sanctuary", 'type': ''},
'CurrencyRef': {'value': 'USD', 'name': 'United States Dollar', 'type': ''},
'CustomerMemo': {'value': 'Thank you for your business and have a great day!'},
'DepartmentRef': None,
'TxnTaxDetail': {'TotalTax': 0,
'TxnTaxCodeRef': {'value': '4', 'name': '', 'type': ''},
'TaxLine': [{'Amount': 0,
'DetailType': 'TaxLineDetail',
'TaxLineDetail': {'PercentBased': True,
'TaxPercent': 0,
'NetAmountTaxable': 35.0,
'TaxRateRef': {'value': '6', 'name': '', 'type': ''}}}]},
'DeliveryInfo': None,
'RecurDataRef': None,
'TaxExemptionRef': {'value': '', 'name': '', 'type': ''},
'MetaData': {'CreateTime': '2025-03-04T18:50:26-08:00',
'LastUpdatedTime': '2025-03-04T18:50:26-08:00',
'LastModifiedByRef': {'value': '9341454203477221'}},
'CustomField': [],
'Line': [{'Id': '1',
'LineNum': 1,
'Description': 'Tree and Shrub Trimming',
'Amount': 35.0,
'DetailType': 'SalesItemLineDetail',
'LinkedTxn': [],
'CustomField': [],
'SalesItemLineDetail': {'UnitPrice': 35,
'Qty': 1,
'ServiceDate': '',
'TaxInclusiveAmt': 0,
'MarkupInfo': None,
'ItemRef': {'value': '18', 'name': 'Trimming', 'type': ''},
'ClassRef': None,
'TaxCodeRef': {'value': 'TAX', 'name': '', 'type': ''},
'PriceLevelRef': None,
'ItemAccountRef': {'value': '45', 'name': 'Landscaping Services'},
'TaxClassificationRef': {'value': 'EUC-9922-V1-6685'}}},
{'Id': None,
'LineNum': 0,
'Description': None,
'Amount': 35.0,
'DetailType': 'SubTotalLineDetail',
'LinkedTxn': [],
'CustomField': [],
'SubtotalLineDetail': None,
'SubTotalLineDetail': {}}],
'LinkedTxn': [],
'AllowOnlinePayment': False,
'SalesTermRef': {'value': '3', 'name': 'Net 30', 'type': ''}}
This has the TaxClassificationRef
"SalesItemLineDetail": { "ItemRef": {"value": "18", "name": "Trimming"}, "TaxCodeRef": {"value": "TAX", "name": ""}, "TaxClassificationRef": {"value": "EUC-9922-V1-6685"} # PRESENT }
Key Difference:
- UAT:
TaxClassificationRef
exists.
- Production:
TaxClassificationRef
is missing.
Debugging the Issue
Step 1: Checking QBO API Docs
I reviewed the QuickBooks Online API documentation and found that TaxClassificationRef
should be returned if the item is taxable. This meant something was suppressing this field in Production.
Step 2: Investigating Python QuickBooks Library
Since I was using the python-quickbooks
library, I suspected it might be causing the issue. I tried fetching the same data directly using Postman.
Step 3: Postman API Test
I created a Postman collection and manually requested:
GET /v3/company/{companyId}/invoice/{invoiceId}
GET /v3/company/{companyId}/item/{itemId}
Results:
- UAT:
TaxClassificationRef
was present.
- Production:
TaxClassificationRef
was missing.
Step 4: Identifying the Root Cause
After experimenting with different API parameters, I found that the minorversion parameter was responsible for the discrepancy.
- UAT does not require
minorversion
to return the latest data.
- Production requires
minorversion=75
to returnTaxClassificationRef
.
Production API Request Without MinorVersion
GET https://quickbooks.api.intuit.com/v3/company/{companyId}/query?query=SELECT * FROM Item WHERE Id = '20812'
Result:
TaxClassificationRef
missing.
Production API Request With MinorVersion
GET https://quickbooks.api.intuit.com/v3/company/{companyId}/query?query=SELECT * FROM Item WHERE Id = '20812'&minorversion=75
Result:
TaxClassificationRef
present.
The Solution: Always Use MinorVersion in Production
To ensure consistent data between UAT and Production, I updated my API queries to explicitly include minorversion=75
in Production.
Updated Python Query
minor_version = "75" if ENV == "production" else None query_url = f"https://quickbooks.api.intuit.com/v3/company/{company_id}/query?query=SELECT * FROM Item WHERE Id = '{item_id}'" if minor_version: query_url += f"&minorversion={minor_version}"
Why This Works
minorversion=75
ensures QBO returns the latest API response format.
- Prevents missing fields like
TaxClassificationRef
in Production.
- UAT works fine without
minorversion
, but including it ensures consistency.
Lessons Learned
- API behaviors can differ between UAT and Production.
- Always cross-check data using Postman or raw API calls.
- Review QuickBooks API documentation for changes in minor versions.
- Explicitly set
minorversion
in Production to avoid missing fields.
- If using the
python-quickbooks
library, verify it supports the latest QBO API changes.
Final Thoughts
This issue was a great reminder that minor versions can impact API responses in unexpected ways. If you're working with QuickBooks Online API and notice missing fields, check if you need to specify a minor version!
Hope this helps others facing similar QBO integration challenges!