Xero is a popular cloud-based accounting software for small to medium-sized businesses. It's been around since 2006 and has grown significantly since then, especially in countries like Australia, New Zealand, and the UK. People love it because it is intuitive to use, can be accessed from anywhere with an internet connection, and integrates with many business apps, including PayPal and Shopify.
In many companies, Xero is just one of the various tools that handle business logic. Therefore, you often need to integrate Xero with other software. For example, you might integrate Xero with your customer relationship management (CRM) software to keep track of your customers' accounts or have Xero integration as part of automation, such as preparing a monthly report of your finances.
Challenges and Complexities of Integrating with the Xero API
This section explores three of the main challenges you might face when working with the Xero API and Python SDK for the first time.
Authenticating and Refreshing the Access Token
Xero uses OAuth 2.0 for authentication, which is good for security but adds complexity. To get started with Xero, you set up an app in the Xero developer portal and get your client ID and secret, which you utilize in the OAuth flow and receive the access token. However, the access tokens are short-lived, usually expiring after thirty minutes. This means you can't just get a token and forget about it. You must implement a system to refresh the token before it expires or your app will lose access.
To do this, you use the scope offline_access
to get a refresh token along with your access token. When your access token expires, you use the refresh token to get a new one. Refresh tokens also expire after sixty days, so you need to implement a system to re-authenticate and get a new refresh token periodically without user interaction.
Securely storing these credentials and tokens can also be challenging. You can't store them in plaintext because that's a security risk. Some examples of more secure options include storing the credentials in a separate config.py
file locally without committing them to version control or loading them via environment variables. You can store the tokens in the browser's local storage or cookies. However, every option has its own drawbacks and challenges, so choose whichever method is most suitable for your use case.
Error-Handling
If you can't handle the different types of errors that may occur as you work with the Xero API, your app might stop working.
The rate limit error is one of the common errors. Xero limits the number of requests you can make in a certain timeframe. You get a 429
error (too many requests) if you hit that limit. Other than this, errors might occur due to authentication issues, lack of permissions, or simply because of bad data.
As the developer writing the Xero integration, you must be intimately familiar with the different types of errors that can occur and write fault-tolerant code that can properly handle the errors. You must also utilize logging efficiently to find and debug errors faster.
Familiarizing with the Request and Response Structure
The request and response structure of the Xero API can be challenging to navigate. Luckily, the SDK handles a lot of the complexity for you, making requests easier to manage.
The SDK is designed to allow you to work with the API in a more Pythonic way. This approach allows you to work with familiar Python objects and methods rather than constructing raw HTTP requests. Here's an example for getting a list of invoices:
invoices = accounting_api.get_invoices(tenant_id)
The SDK handles forming the HTTP request, including headers and authentication, and parses the response.
Even though the SDK does most of the work, you still need to know the API endpoints, the SDK methods they correspond to, the required parameters, and the fields present in the response objects. This means you need to spend a lot of time reviewing the API and SDK docs. You must also ensure your data is in the correct format before passing it to the SDK. This can involve complex preprocessing, especially if you are integrating with other products that have their own data structure.
Authentication with Xero API
To get started with Xero API, you need to set up your app in the Xero developer portal and get your client ID and client secret.
The Xero API supports three OAuth 2.0 grant types:
- Authorization code flow (for web apps)
- Proof Key for Code Exchange (PKCE) flow (for mobile and desktop apps)
- Client credentials flow (premium integration option)
For a web app, you'd typically use the authorization code flow.
Here is the standard authorization flow for authorizing an app for the first time. Luckily, the SDK handles many of these steps:
Image courtesy of Xero Developer
Then, to use the xero-python
SDK, you need to configure the api_client
:
# Setup the api_client
api_client = ApiClient(
Configuration(
debug=false,
oauth2_token=OAuth2Token(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET"
),
),
pool_threads=1,
)
api_client.set_oauth2_token("YOUR_ACCESS_TOKEN")
# Setup the accounting_api instance
accounting_api = AccountingApi(api_client)
Xero API Methods: Accounts Receivable Use Case
Let's look at how to use the Xero API, with some examples focused on accounts receivable. Accounts receivable is a general accounting term that refers to the money owed to your business by customers for goods or services you've provided but haven't been paid for yet.
When working with accounts receivable in the Xero API, you need to use a combination of endpoints to manage the process fully, such as creating invoices, recording payments, and viewing bank transactions.
The official Xero documentation contains a comprehensive list of all the methods available for the Python SDK.
Create, Get, and Update Accounts
You use the create_account
method to create a new account. At a minimum, you need to pass in the tenant ID and an Account
object containing the details for the new account. Here's an example:
from xero_python.accounting import Account
new_account = Account(
name = "My New Account",
code = "1234",
type = AccountType.EXPENSE,
description = "This is a test account"
)
created_account = accounting_api.create_account(
xero_tenant_id,
account = new_account
)
To retrieve accounts, you can use the get_accounts
method:
accounts = accounting_api.get_accounts(xero_tenant_id)
# Specify the account ID to get a single account
account = accounting_api.get_account(xero_tenant_id, account_id)
This returns a list of all accounts, but you can also pull out a single account using the get_account
method (not plural) by passing in an account ID.
To update an account, you use the update_account
method:
updated_account = Account(
account_id = account_id,
name = "Updated Account Name",
description = "This account has been updated"
)
result = accounting_api.update_account(
xero_tenant_id,
account_id,
account = updated_account
)
This creates an Account
object with the updated details for the account and then passes this object to the update_account
method along with the tenant ID and account ID.
Create, Get, and Update Invoices
Before you can create an invoice, you need to set up an Invoice
object, adding your details, like the contact, line items, and due date. When adding in the details for each line item on the invoice, you create a LineItem
object and pass this in as a list in the Invoice
object.
Once the object is set up, you just use the create_invoices
method. Here's an example that also pulls in information on a contact by adding a Contact
object:
from xero_python.accounting import Invoice, LineItem, Contact
contact = Contact(contact_id = "contact-uuid-here")
line_item = LineItem(
description = "Cool stuff",
quantity = 1,
unit_amount = 100,
account_code = "200"
)
new_invoice = Invoice(
type = "ACCREC",
contact = contact,
line_items = [line_item],
status = "DRAFT"
)
created_invoice = accounting_api.create_invoices(xero_tenant_id, invoices = [new_invoice])
To retrieve invoices, you use the get_invoices
method:
invoices = accounting_api.get_invoices(xero_tenant_id)
This gives you a list of all invoices. You can add filters or use get_invoice
with an invoice ID if you want a specific one.
Updating an invoice is similar to creating one. You make an Invoice
object with the changes you want, then use update_invoice
:
updated_invoice = Invoice(
status = "AUTHORISED"
)
result = accounting_api.update_invoice(xero_tenant_id, invoice_id, invoices = [updated_invoice])
This updates the invoice status to AUTHORISED
. You can also change other things, like adding line items or editing the due date.
Create and Get Payments
The Payment
object allows you to specify the details of a payment, such as the amount and the date; and the create_payment
creates the payment by passing in the Payment
object and the tenant ID. It looks something like this:
from xero_python.accounting import Payment
new_payment = Payment(
invoice = Invoice(invoice_id = "invoice-uuid"),
account = Account(account_id = "account-uuid"),
amount = 100.00,
date = datetime.now()
)
created_payment = accounting_api.create_payment(xero_tenant_id, payment = new_payment)
This creates a payment of $100 USD against a specific invoice from a specific account. The SDK sends it to Xero and gives you back the created payment info.
To get payments, you use the get_payments
method, and if you want details on a specific payment, use get_payment
with the payment ID:
# Get all payments
payments = accounting_api.get_payments(xero_tenant_id)
# Get a specific payment
specific_payment = accounting_api.get_payment(xero_tenant_id, payment_id)
This gives you a list of all payments. You can add filters if you want specific ones. For example, this is how to find all payments of $100 USD or more:
filtered_payments = accounting_api.get_payments(xero_tenant_id, where="Amount >= 100")
Create and Get Bank Transfers
Bank transfers in Xero are used to track money transfers between banks. To create a bank transfer, you use the create_bank_transfer
method, where you need to declare the bank accounts using the Account
class. The BankTransfer
class is used to specify the source and destination accounts. Multiple transfers can be created together using the BankTransfers
class:
fromBankAccount = Account(
account_id = "XXXXXX")
toBankAccount = Account(
account_id = "XXXXXX")
bank_transfer = BankTransfer(
from_bank_account = fromBankAccount,
to_bank_account = toBankAccount,
amount = 1.0)
bankTransfers = BankTransfers(
bank_transfers = [bank_transfer])
api_response = api_instance.create_bank_transfer(xero_tenant_id, bankTransfers)
The get_bank_transfers
method can be used to find all bank transfers:
api_response = api_instance.get_bank_transfers(xero_tenant_id)
You can also pass filters to find specific transfers. For example, here is how to find all the transfers that have an attachment:
api_response = api_instance.get_bank_transfers(xero_tenant_id, where="HasAttachments==true")
To get a specific transfer, you can use the get_bank_transfer
and pass the transfer ID:
api_response = api_instance.get_bank_transfer(xero_tenant_id, bank_transfer_id)
Create and Get Bank Transactions
Bank transactions in Xero are about recording money moving in and out of your bank accounts. Unlike payments, they're not necessarily tied to invoices or bills.
To create a bank transaction, use the BankTransaction
object and the create_bank_transaction
method, as in the following example:
from xero_python.accounting import BankTransaction, LineItem, Account, Contact
line_item = LineItem(description = "Office lunch", quantity = 1, unit_amount = 50)
bank_account = Account(account_id = "bank-account-uuid-here")
contact = Contact(contact_id = "contact-uuid-here")
new_transaction = BankTransaction(
type = "SPEND",
contact = contact,
line_items = [line_item],
bank_account = bank_account,
date = datetime.now().date(),
status = "AUTHORISED",
line_amount_types = "Inclusive"
)
created_transaction = accounting_api.create_bank_transaction(xero_tenant_id, bank_transactions = [new_transaction])
This example creates a new bank transaction for an office lunch by passing in a few details to the BankTransaction
object, including objects for line item details, which bank account it corresponds to, and the related contact info.
The get_bank_transactions
method retrieves a list of bank transactions, and if you add get_bank_transaction
with a transaction ID, you can get details on a specific transaction:
# Get all bank transactions
transactions = accounting_api.get_bank_transactions(xero_tenant_id)
# Get a specific bank transaction
specific_transaction = accounting_api.get_bank_transaction(xero_tenant_id, bank_transaction_id)
You can also add filters. For example, the following code retrieves all the transactions with the type "RECEIVE"
:
filtered_transactions = accounting_api.get_bank_transactions(xero_tenant_id, where = "Type==\"RECEIVE\"")
Conclusion
This guide provided an overview of integrating with the Xero API using the Python SDK and demonstrated how to use some methods needed to manage Accounts Receivable.
The Xero API is one of the most robust, well-documented accounting APIs available. However, there are some challenges and complexities that cannot be overlooked. Using Apideck, you can simplify and significantly reduce the time it takes to implement Xero integrations. Instead of wrestling with Xero API directly, sign up for Apideck and take advantage of the Apideck Unified Accounting APIs and official SDKs for popular languages, including Python.
Ready to get started?
Scale your integration strategy and deliver the integrations your customers need in record time.