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': 'hoa.vuquang@glinteco.com'},
 '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': 'info@glinteco.com'},
 '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:
 

  1. GET /v3/company/{companyId}/invoice/{invoiceId}
     
  2. 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 return TaxClassificationRef.
     
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

  1. API behaviors can differ between UAT and Production.
     
  2. Always cross-check data using Postman or raw API calls.
     
  3. Review QuickBooks API documentation for changes in minor versions.
     
  4. Explicitly set minorversion in Production to avoid missing fields.
     
  5. 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!

Let me know if you have any questions!