Leveraging Python for QuickBooks API: A Case Study on Invoice Management
By manhnv, at: 18:34 Ngày 15 tháng 3 năm 2024
Leveraging Python for QuickBooks API: A Case Study on Invoice Management
Introduction
Welcome to this comprehensive guide on how to use Python to interact with the QuickBooks API. In this blog, we will not only introduce the basics of making API calls to QuickBooks with Python, but also walk you through a real-world case study that highlights the power and flexibility of this approach.
Consider this scenario: You are a business owner who uses QuickBooks for book keeping. There are hundreds of invoices monthly. One day, you realize that you need to update a specific detail (ex: tax value) on a large number of invoices. The thought of manually opening each invoice on the QuickBooks main page and making the changes is daunting. You wish there was a way to automate this process.
This is where Python and the QuickBooks API come into play. With these tools, you can write a script that fetches the invoices and makes the necessary changes, all without having to navigate through the QuickBooks UI.
In this guide, we will show you how to set up your environment, authenticate with the QuickBooks API, fetch invoices, and update them. We will cover the basics of the QuickBooks API, error handling, and best practices for structuring your code.
By the end of this guide, you will have a solid understanding of how to use Python to interact with the QuickBooks API, and you will be able to apply this knowledge to solve your own business problems. So, let’s dive in and start our journey towards automation and efficiency!
NOTE: in order to follow the guide eazily, you should have your system installed python and pyenv first You also need to Create a Quickbooks app to retrieve the tokens from that app. If you don’t know how, you can follow this guide.
QuickBooks API Overview
The QuickBooks Online API is a RESTful web service that uses OAuth 2.0 for authentication and authorization. It allows third-party applications to interact with the QuickBooks Online platform, enabling them to perform a variety of tasks such as querying data, posting transactions, and more.
The API uses standard HTTP methods like GET
, POST
, PUT
, and DELETE
to interact with QuickBooks Online resources. Each resource in QuickBooks Online is associated with an endpoint, and you can perform operations on these resources by making requests to their corresponding endpoints.
Some useful API endpoints to use are:
Vendor
- Endpoint:
/v3/company/{companyId}/vendor
- Methods:
GET
,POST
- Description: The Vendor endpoint allows you to manage vendors in your QuickBooks Online company. You can retrieve vendor details, create new vendors, update existing vendors, and delete vendors.
Invoice
- Endpoint:
/v3/company/{companyId}/invoice
- Methods:
GET
,POST
- Description: The Invoice endpoint allows you to manage invoices. You can retrieve invoice details, create new invoices, update existing invoices, and delete invoices.
Customer
- Endpoint:
/v3/company/{companyId}/customer
- Methods:
GET
,POST
- Description: The Customer endpoint allows you to manage customers in your QuickBooks Online company. You can retrieve customer details, create new customers, update existing customers, and delete customers.
CompanyInfo
- Endpoint:
/v3/company/{realm_id}/companyinfo/{realm_id}
- Methods:
GET
,POST
- Description: The CompanyInfo endpoint allows you to manage and change your Company Information. You can retrieve Company details, full or sparse update your Company
Setup the testing environment
First to go, we need to setup our testing environments with the package python-quickbooks
# create the virtual environment for development
pyenv virtualenv 3.10.8 python_quickbooks # you can change 3.10.8 with your installed version of Python
pyenv activate python_quickbooks
# install neccessary dependencies
pip install requests # library for making HTTP(S) request in python
pip install ipython # an interactive shell, alternate to default python shell, much more useful
Now , after all the process are done, you can now access the new shell with command:
ipython
Start making API call to Quickbooks
NOTE: In this tutorial, I will use the Invoice endpoint. Before we start to integrate our app with Quickbooks, we first need to generate a
access_token
to use for calling Quickbooks API. Goto Quickbooks Developer Playground and follow the steps to create a new one.
Type the code below to your shell (or copy them and paste directly to your shell)
import requests
realm_id = "paste your realm id here"
access_token = "paste your access_token here"
query = "select * from Invoice"
sanbox_url = "
<https: sandbox-quickbooks.api.intuit.com="">"
url = f"{sanbox_url}/v3/company/{realm_id}/query?query={query}&minorversion=70"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
</https:>response = requests.get(url, headers=headers)
print(response.json())
The code above are very simple:
import requests
: This line imports therequests
library, which is a popular Python library for making HTTP requests.
realm_id = "paste your realm id here"
: This line initializes a variable namedrealm_id
and assigns it a string value "paste your realm id here". You should replace this string with the actual realm ID for your QuickBooks company.
access_token = "paste your access_token here"
: This line initializes a variable namedaccess_token
and assigns it a string value "paste your access_token here". You should replace this string with the actual access token for your QuickBooks API access.
query = "select * from Invoice"
: This line initializes a variable namedquery
and assigns it a SQL-like query string. In this case, it's querying all columns (``) from theInvoice
table. This is the query you want to execute on your QuickBooks data.
sandbox_url = "<https: sandbox-quickbooks.api.intuit.com="">"</https:>
: This line initializes a variable namedsandbox_url
and assigns it the URL of the QuickBooks API sandbox environment. In a real-world scenario, you might use a different URL for the production environment.
url = f"{sanbox_url}/v3/company/{realm_id}/query?query={query}&minorversion=70"
: This line constructs the URL for making a query request to the QuickBooks API. It uses an f-string to interpolate thesandbox_url
,realm_id
,query
, and other necessary components to form the complete URL. Theminorversion=70
parameter specifies the minor version of the API being used.
headers = { "Authorization": f"Bearer {access_token}", "Accept": "application/json" }
: This line initializes a dictionary namedheaders
containing HTTP headers that will be included in the request. TheAuthorization
header includes the access token necessary for authenticating the request, and theAccept
header specifies that the client expects JSON-formatted responses.
response = requests.get(url, headers=headers)
: This line makes an HTTP GET request to the URL constructed earlier (url
). It includes the headers specified in theheaders
dictionary. The response from the server is stored in the variableresponse
.
print(response.json())
: This line prints the JSON content of the response received from the QuickBooks API server. Thejson()
method of theresponse
object parses the response content and returns it as a Python dictionary, which is then printed to the console. This should give you the result of the query executed on your QuickBooks data, in JSON format.
After calling the API, you should see the data below printed to your terminal. In case you are wondering, this API responses to you about all the Invoice
objects in your Quickbooks account.
{'QueryResponse': {'Invoice': [{'AllowIPNPayment': False,
'AllowOnlinePayment': False,
'AllowOnlineCreditCardPayment': False,
'AllowOnlineACHPayment': False,
'domain': 'QBO',
'sparse': False,
'Id': '159',
'SyncToken': '1',
'MetaData': {'CreateTime': '2024-03-13T02:46:50-07:00',
'LastModifiedByRef': {'value': '9130355782826476'},
'LastUpdatedTime': '2024-03-13T02:46:50-07:00'},
'CustomField': [],
'DocNumber': '1049',
'TxnDate': '2024-03-13',
'CurrencyRef': {'value': 'USD', 'name': 'United States Dollar'},
'LinkedTxn': [{'TxnId': '160', 'TxnType': 'Payment'},
{'TxnId': '101', 'TxnType': 'Payment'},
{'TxnId': '31', 'TxnType': 'Payment'},
{'TxnId': '72', 'TxnType': 'Payment'}],
'Line': [{'Id': '1',
'LineNum': 1,
'Amount': 1111.0,
'DetailType': 'SalesItemLineDetail',
'SalesItemLineDetail': {'ItemRef': {'value': '2', 'name': 'Hours'},
'UnitPrice': 1111,
'Qty': 1,
'ItemAccountRef': {'value': '1', 'name': 'Services'},
'TaxCodeRef': {'value': 'TAX'}}},
{'Id': '2',
'LineNum': 2,
'Description': 'Installation of landscape design',
'Amount': 50.0,
'DetailType': 'SalesItemLineDetail',
'SalesItemLineDetail': {'ItemRef': {'value': '7',
'name': 'Installation'},
'UnitPrice': 50,
'Qty': 1,
'ItemAccountRef': {'value': '52',
'name': 'Landscaping Services:Labor:Installation'},
'TaxCodeRef': {'value': 'TAX'}}},
{'Amount': 1161.0,
'DetailType': 'SubTotalLineDetail',
'SubTotalLineDetail': {}}],
'TxnTaxDetail': {'TxnTaxCodeRef': {'value': '2'},
'TotalTax': 92.88,
'TaxLine': [{'Amount': 92.88,
'DetailType': 'TaxLineDetail',
'TaxLineDetail': {'TaxRateRef': {'value': '3'},
'PercentBased': True,
'TaxPercent': 8,
'NetAmountTaxable': 1161.0}}]},
'CustomerRef': {'value': '1', 'name': "Amy's Bird Sanctuary"},
'CustomerMemo': {'value': 'Thank you for your business and have a great day!'},
'BillAddr': {'Id': '2',
'Line1': '4581 Finch St.',
'City': 'Bayshore',
'CountrySubDivisionCode': 'CA',
'PostalCode': '94326',
'Lat': 'INVALID',
'Long': 'INVALID'},
'ShipAddr': {'Id': '2',
'Line1': '4581 Finch St.',
'City': 'Bayshore',
'CountrySubDivisionCode': 'CA',
'PostalCode': '94326',
'Lat': 'INVALID',
'Long': 'INVALID'},
'FreeFormAddress': True,
'SalesTermRef': {'value': '3', 'name': 'Net 30'},
'DueDate': '2024-04-12',
'TotalAmt': 1253.88,
'ApplyTaxAfterDiscount': False,
'PrintStatus': 'NeedToPrint',
'EmailStatus': 'NotSet',
'BillEmail': {'Address': '[email protected]'},
'Balance': 720.88}],
'startPosition': 1,
'maxResults': 1,
'totalCount': 1},
'time': '2024-03-13T02:47:02.837-07:00'}
Now, you can see that the information about all the invoices in my QuickBooks account is printed in your terminal. As you can see, I only have one Invoice
with an ID of ‘159’
and a SyncToken
of "1"
. Now, we should save the invoice ID and SyncToken into variables for later use.
We've examined the GET request to QuickBooks. Next, we should explore how to perform a POST request to QuickBooks. The main purpose is to modify some properties of the Invoice
using the API (e.g., PrivateNote
, DocNumber
…). Just to be clear, in this tutorial, I will update the PrivateNote
of the current Invoice
.
Let's write some code with slight modifications, this time utilizing the POST method to make the API call.
invoice_id = "the invoice's Id from the previous step"
invoice_sync_token = "the invoice's SyncToken from the previous step"
url = f"{sanbox_url}/v3/company/{realm_id}/invoice?minorversion=70"
data = {
"SyncToken": invoice_sync_token,
"Id": invoice_id,
"sparse": True,
"PrivateNote": "Private Note",
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
Let's analyze this code a little bit:
invoice_id = "the invoice's Id from the previous step"
: This line initializes a variable namedinvoice_id
and assigns it a string value. This string should represent the ID of the invoice obtained from a previous step or fetched from your data source.invoice_sync_token = "the invoice's SyncToken from the previous step"
: This line initializes a variable namedinvoice_sync_token
and assigns it a string value. This string should represent the SyncToken of the invoice obtained from a previous step or fetched from your data source.url = f"{sanbox_url}/v3/company/{realm_id}/invoice?minorversion=70"
: This line constructs the URL for making a POST request to update an invoice in the QuickBooks API. It uses an f-string to interpolate thesandbox_url
,realm_id
, and other necessary components to form the complete URL. The endpoint/invoice
is used to update an invoice.data = { "SyncToken": invoice_sync_token, "Id": invoice_id, "sparse": True, "PrivateNote": "Private Note", }
: This line initializes a dictionary nameddata
containing the payload to be sent in the request body. To be more specific:SyncToken
: the invoice’sSyncToken
to determine that the versions ofInvoice
that you’re going to update is the newest versionId
: the invoice’sId
to determine which Invoice you want to updatesparse
: a boolean variable to let QuickBooks know that you only want to update some of the Invoice information, not the whole InvoicePrivateNote
: the attribute and its value of theInvoice
that you want to update
response = requests.post(url, headers=headers, json=data)
: This line makes an HTTP POST request to the URL constructed earlier (url
). It includes the headers specified in theheaders
dictionary and the payload specified in thedata
dictionary as JSON data. This request is used to update the invoice with the provided data.print(response.json())
: This line prints the JSON content of the response received from the QuickBooks API server. Thejson()
method of theresponse
object parses the response content and returns it as a Python dictionary, which is then printed to the console. This should give you the response of the invoice update operation, including any errors or success messages.
Hence, after executed the code above, you should see the following response from the terminal:
{'Invoice': {'AllowIPNPayment': False,
'AllowOnlinePayment': False,
'AllowOnlineCreditCardPayment': False,
'AllowOnlineACHPayment': False,
'InvoiceLink': '<https: comingsoonview="" developer.intuit.com="" scs-v1-494225558c3846d1a24e8e06e6cd613f29e0f7d6b3844af1a22d00fd397dd424c6d545d13ecd4c50b0dde49772cb5b17="">',
'domain': 'QBO',
'sparse': False,
'Id': '159',
'SyncToken': '2',
'MetaData': {'CreateTime': '2024-03-13T02:46:50-07:00',
'LastModifiedByRef': {'value': '9130355782826476'},
'LastUpdatedTime': '2024-03-13T05:05:05-07:00'},
'CustomField': [],
'DocNumber': '1049',
'TxnDate': '2024-03-13',
'CurrencyRef': {'value': 'USD', 'name': 'United States Dollar'},
'PrivateNote': 'Private Note',
'LinkedTxn': [{'TxnId': '160', 'TxnType': 'Payment'},
{'TxnId': '101', 'TxnType': 'Payment'},
{'TxnId': '31', 'TxnType': 'Payment'},
{'TxnId': '72', 'TxnType': 'Payment'}],
'Line': [{'Id': '1',
'LineNum': 1,
'Amount': 1111.0,
'DetailType': 'SalesItemLineDetail',
'SalesItemLineDetail': {'ItemRef': {'value': '2', 'name': 'Hours'},
'UnitPrice': 1111,
'Qty': 1,
'ItemAccountRef': {'value': '1', 'name': 'Services'},
'TaxCodeRef': {'value': 'TAX'}}},
{'Id': '2',
'LineNum': 2,
'Description': 'Installation of landscape design',
'Amount': 50.0,
'DetailType': 'SalesItemLineDetail',
'SalesItemLineDetail': {'ItemRef': {'value': '7', 'name': 'Installation'},
'UnitPrice': 50,
'Qty': 1,
'ItemAccountRef': {'value': '52',
'name': 'Landscaping Services:Labor:Installation'},
'TaxCodeRef': {'value': 'TAX'}}},
{'Amount': 1161.0,
'DetailType': 'SubTotalLineDetail',
'SubTotalLineDetail': {}}],
'TxnTaxDetail': {'TxnTaxCodeRef': {'value': '2'},
'TotalTax': 92.88,
'TaxLine': [{'Amount': 92.88,
'DetailType': 'TaxLineDetail',
'TaxLineDetail': {'TaxRateRef': {'value': '3'},
'PercentBased': True,
'TaxPercent': 8,
'NetAmountTaxable': 1161.0}}]},
'CustomerRef': {'value': '1', 'name': "Amy's Bird Sanctuary"},
'CustomerMemo': {'value': 'Thank you for your business and have a great day!'},
'BillAddr': {'Id': '2',
'Line1': '4581 Finch St.',
'City': 'Bayshore',
'CountrySubDivisionCode': 'CA',
'PostalCode': '94326',
'Lat': 'INVALID',
'Long': 'INVALID'},
'ShipAddr': {'Id': '2',
'Line1': '4581 Finch St.',
'City': 'Bayshore',
'CountrySubDivisionCode': 'CA',
'PostalCode': '94326',
'Lat': 'INVALID',
'Long': 'INVALID'},
'FreeFormAddress': True,
'SalesTermRef': {'value': '3', 'name': 'Net 30'},
'DueDate': '2015-09-30',
'TotalAmt': 1253.88,
'ApplyTaxAfterDiscount': False,
'PrintStatus': 'NeedToPrint',
'EmailStatus': 'NotSet',
'BillEmail': {'Address': '[email protected]'},
'Balance': 720.88},
'time': '2024-03-13T05:05:05.027-07:00'}</https:>
Here, you can see that my Invoice
now has the PrivateNote
attribute and its value is “Private Note”. The SyncToken
also change from “1” to “2”, show that you’ve update the Invoice
, and now the newest version of the Invoice
is version “2”.
Conclusion
If you read this far, congratulations, you now know how to make API calls to Quickbooks using python. This is a very useful knowledge and we can apply this to many other things, for example integrating Django App with Quickbooks (this will also be something I will talk about in the next blog ^^ ).
Happy Coding!