Package: xero-python
xero-pythonThe xero-python SDK makes it easy for developers to access Xero's APIs in their python code, and build robust applications and software using small business & general ledger accounting data.Table of ContentsAPI Client documentationSample ApplicationsXero Account RequirementsInstallationConfigurationAuthenticationCustom ConnectionsApp Store SubscriptionsAPI ClientsHelper MethodsUsage ExamplesSDK conventionsParticipating in Xero’s developer communityContributingAPI Client documentationThis SDK supports full method coverage for the following Xero API sets:API SetDescriptionAccountingThe Accounting API exposes accounting functions of the main Xero application(most commonly used)AssetsThe Assets API exposes fixed asset related functions of the Xero Accounting applicationFilesThe Files API provides access to the files, folders, and the association of files within a Xero organisationProjectsXero Projects allows businesses to track time and costs on projects/jobs and report on profitabilityPayroll (AU)The (AU) Payroll API exposes payroll related functions of the payroll Xero applicationPayroll (UK)The (UK) Payroll API exposes payroll related functions of the payroll Xero applicationPayroll (NZ)The (NZ) Payroll API exposes payroll related functions of the payroll Xero applicationSample ApplicationsSample apps can get you started quickly with simple auth flows and advanced usage examples.Sample AppDescriptionScreenshotstarter-appBasic getting started code samplesfull-appComplete app with more complex examplescustom-connections-starterBasic app showing Custom Connections - a Xeropremium optionfor building M2M integrations to a single orgXero Account RequirementsCreate afree Xero user accountLogin to your Xero developerdashboardand create an API applicationCopy the credentials from your API app and store them using a secure ENV variable strategyDecide theneccesary scopesfor your app's functionalityInstallationTo install this SDK in your project:pip install xero-pythonConfiguration# -*- coding: utf-8 -*-importosfromfunctoolsimportwrapsfromioimportBytesIOfromlogging.configimportdictConfigfromflaskimportFlask,sessionfromflask_oauthlib.contrib.clientimportOAuth,OAuth2Applicationfromflask_sessionimportSessionfromxero_python.accountingimportAccountingApifromxero_python.assetsimportAssetApifromxero_python.projectimportProjectApifromxero_python.payrollauimportPayrollAuApifromxero_python.payrollukimportPayrollUkApifromxero_python.payrollnzimportPayrollNzApifromxero_python.fileimportFilesApifromxero_python.api_clientimportApiClient,serializefromxero_python.api_client.configurationimportConfigurationfromxero_python.api_client.oauth2importOAuth2Tokenfromxero_python.exceptionsimportAccountingBadRequestException,PayrollUkBadRequestExceptionfromxero_python.identityimportIdentityApifromxero_python.utilsimportgetvalueimportlogging_settingsfromutilsimportjsonify,serialize_modeldictConfig(logging_settings.default_settings)# configure main flask applicationapp=Flask(__name__)app.config.from_object("default_settings")app.config.from_pyfile("config.py",silent=True)ifapp.config["ENV"]!="production":# allow oauth2 loop to run over http (used for local testing only)os.environ["OAUTHLIB_INSECURE_TRANSPORT"]="1"# configure persistent session cacheSession(app)# configure flask-oauthlib applicationoauth=OAuth(app)xero=oauth.remote_app(name="xero",version="2",client_id=app.config["CLIENT_ID"],client_secret=app.config["CLIENT_SECRET"],endpoint_url="https://api.xero.com/",authorization_url="https://login.xero.com/identity/connect/authorize",access_token_url="https://identity.xero.com/connect/token",refresh_token_url="https://identity.xero.com/connect/token",scope="offline_access openid profile email accounting.transactions ""accounting.transactions.read accounting.reports.read ""accounting.journals.read accounting.settings accounting.settings.read ""accounting.contacts accounting.contacts.read accounting.attachments ""accounting.attachments.read assets projects ""files ""payroll.employees payroll.payruns payroll.payslip payroll.timesheets payroll.settings",)# type: OAuth2Application# configure xero-python sdk clientapi_client=ApiClient(Configuration(debug=app.config["DEBUG"],oauth2_token=OAuth2Token(client_id=app.config["CLIENT_ID"],client_secret=app.config["CLIENT_SECRET"]),),pool_threads=1,)# configure token persistence and exchange point between flask-oauthlib and xero-python@xero.tokengetter@api_client.oauth2_token_getterdefobtain_xero_oauth2_token():returnsession.get("token")@xero.tokensaver@api_client.oauth2_token_saverdefstore_xero_oauth2_token(token):session["token"]=tokensession.modified=TrueAuthenticationAll API requests go through Xero's OAuth 2.0 gateway and require a validaccess_tokento be set on theclientwhich appends theaccess_tokenJWTto the header of each request.If you are making an API call for the first time:Send the user to the Xero authorization URL@app.route("/login")deflogin():redirect_url=url_for("oauth_callback",_external=True)session["state"]=app.config["STATE"]try:response=xero.authorize(callback_uri=redirect_url,state=session["state"])exceptExceptionase:print(e)raisereturnresponseThe user will authorize your application and be sent to yourredirect_uri. This is when and where to check that the returned "state" param matches that which was previously defined. If the "state" params match, calling the oauth library'sauthorized_response()method will swap the temporary auth code for an access token which you can store and use for subsequent API calls.@app.route("/callback")defoauth_callback():ifrequest.args.get("state")!=session["state"]:return"Error, state doesn't match, no token for you."try:response=xero.authorized_response()exceptExceptionase:print(e)raiseifresponseisNoneorresponse.get("access_token")isNone:return"Access denied: response=%s"%responsestore_xero_oauth2_token(response)returnredirect(url_for("index",_external=True))Call the Xero API like so:@app.route("/accounting_invoice_read_all")@xero_token_requireddefaccounting_invoice_read_all():code=get_code_snippet("INVOICES","READ_ALL")#[INVOICES:READ_ALL]xero_tenant_id=get_xero_tenant_id()accounting_api=AccountingApi(api_client)try:invoices_read=accounting_api.get_invoices(xero_tenant_id)exceptAccountingBadRequestExceptionasexception:output="Error: "+exception.reasonjson=jsonify(exception.error_data)else:output="Total invoices found:{}.".format(len(invoices_read.invoices))json=serialize_model(invoices_read)#[/INVOICES:READ_ALL]returnrender_template("output.html",title="Invoices",code=code,output=output,json=json,len=0,set="accounting",endpoint="invoice",action="read_all")It is recommended that you store this token set JSON in a datastore in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API, you will need to access the previously generated token set, initialize it on the SDKclient, and refresh theaccess_tokenprior to making API calls.Token Setkeyvaluedescriptionid_token:"xxx.yyy.zzz"OpenID Connecttoken returned ifopenid profile emailscopes acceptedaccess_token:"xxx.yyy.zzz"Bearer tokenwith a 30 minute expiration required for all API callsexpires_in:1800Time in seconds till the token expires - 1800s is 30mrefresh_token:"XXXXXXX"Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiryscope:["email", "profile", "openid", "accounting.transactions", "offline_access"]The Xero permissions that are embedded in theaccess_tokenExample Token Set JSON:{
  "id_token": "xxx.yyy.zz",
  "access_token": "xxx.yyy.zzz",
  "expires_in": 1800,
  "token_type": "Bearer",
  "refresh_token": "xxxxxxxxx",
  "scope": ["email", "profile", "openid", "accounting.transactions", "offline_access"]
}Custom ConnectionsCustom Connections are a Xeropremium optionused for building M2M integrations to a single organisation. A custom connection uses OAuth 2.0'sclient_credentialsgrant which eliminates the step of exchanging the temporary code for a token set.To use this SDK with a Custom Connections:# -*- coding: utf-8 -*-importosfromfunctoolsimportwrapsfromioimportBytesIOfromlogging.configimportdictConfigfromflaskimportFlask,url_for,render_template,session,redirect,json,send_filefromflask_sessionimportSessionfromxero_python.accountingimportAccountingApi,ContactPerson,Contact,Contactsfromxero_python.api_clientimportApiClient,serializefromxero_python.api_client.configurationimportConfigurationfromxero_python.api_client.oauth2importOAuth2Tokenfromxero_python.exceptionsimportAccountingBadRequestExceptionfromxero_python.identityimportIdentityApifromxero_python.utilsimportgetvalueimportlogging_settingsfromutilsimportjsonify,serialize_modeldictConfig(logging_settings.default_settings)# configure main flask applicationapp=Flask(__name__)app.config.from_object("default_settings")app.config.from_pyfile("config.py",silent=True)# configure persistent session cacheSession(app)# configure xero-python sdk clientapi_client=ApiClient(Configuration(debug=app.config["DEBUG"],oauth2_token=OAuth2Token(client_id=app.config["CLIENT_ID"],client_secret=app.config["CLIENT_SECRET"]),),pool_threads=1,)# configure token persistence and exchange point between app session and xero-python@api_client.oauth2_token_getterdefobtain_xero_oauth2_token():returnsession.get("token")@api_client.oauth2_token_saverdefstore_xero_oauth2_token(token):session["token"]=tokensession.modified=True@app.route("/get_token")defget_token():try:# no user auth flow, no exchanging temp code for tokenxero_token=api_client.get_client_credentials_token()exceptExceptionase:print(e)raise# todo validate state valueifxero_tokenisNoneorxero_token.get("access_token")isNone:return"Access denied: response=%s"%xero_tokenstore_xero_oauth2_token(xero_token)returnredirect(url_for("index",_external=True))@app.route("/accounting_invoice_read_all")@xero_token_requireddefaccounting_invoice_read_all():code=get_code_snippet("INVOICES","READ_ALL")#[INVOICES:READ_ALL]accounting_api=AccountingApi(api_client)try:invoices_read=accounting_api.get_invoices('')exceptAccountingBadRequestExceptionasexception:output="Error: "+exception.reasonjson=jsonify(exception.error_data)else:output="Total invoices found:{}.".format(len(invoices_read.invoices))json=serialize_model(invoices_read)#[/INVOICES:READ_ALL]returnrender_template("output.html",title="Invoices",code=code,output=output,json=json,len=0,set="accounting",endpoint="invoice",action="read_all")Because Custom Connections are only valid for a single organisation you don't need to pass the xero-tenant-id as the first parameter to every method, or more specifically for this SDK xeroTenantId can be an empty string.Because the SDK is generated from the OpenAPI spec the parameter remains. For now you are required to pass an empty string to use this SDK with a Custom Connection.App Store SubscriptionsIf you are implementing subscriptions to participate in Xero's App Store you will need to setupApp Store subscriptionsendpoints.When a plan is successfully purchased, the user is redirected back to the URL specified in the setup process. The Xero App Store appends the subscription Id to this URL so you can immediately determine what plan the user has subscribed to through the subscriptions API.With your app credentials you can create a client viaclient_credentialsgrant_type with themarketplace.billingscope. This unique access_token will allow you to query any functions inAppStoreApi. Client Credentials tokens to query app store endpoints will only work for apps that have completed the App Store on-boarding process.# configure xero-python sdk clientapi_client=ApiClient(Configuration(debug=app.config["DEBUG"],oauth2_token=OAuth2Token(client_id=app.config["CLIENT_ID"],client_secret=app.config["CLIENT_SECRET"]),),pool_threads=1,)try:# pass True for app_store_billing - defaults to False if no value providedxero_token=api_client.get_client_credentials_token(True)exceptExceptionase:print(e)raiseapp_store_api=AppStoreApi(api_client)subscription=app_store_api.get_subscription(subscription_id)print(subscription){'current_period_end':datetime.datetime(2021,9,2,14,8,58,772536,tzinfo=tzutc()),'end_date':None,'id':'03bc74f2-1237-4477-b782-2dfb1a6d8b21','organisation_id':'79e8b2e5-c63d-4dce-888f-e0f3e9eac647','plans':[{'id':'6abc26f3-9390-4194-8b25-ce8b9942fda9','name':'Small','status':'ACTIVE','subscription_items':[{'end_date':None,'id':'834cff4c-b753-4de2-9e7a-3451e14fa17a','price':{'amount':Decimal('0.1000'),'currency':'NZD','id':'2310de92-c7c0-4bcb-b972-fb7612177bc7'},'product':{'id':'9586421f-7325-4493-bac9-d93be06a6a38','name':'','type':'FIXED','seat_unit':None},'start_date':datetime.datetime(2021,8,2,14,8,58,772536,tzinfo=tzutc()),'test_mode':True}]}],'start_date':datetime.datetime(2021,8,2,14,8,58,772536,tzinfo=tzutc()),'status':'ACTIVE','test_mode':True}You should use the subscription data to provision user access/permissions to your application.App Store Subscription WebhooksIn additon to a subscription Id being passed through the URL, when a purchase or an upgrade takes place you will be notified via a webhook. You can then use the subscription Id in the webhook payload to query the AppStore endpoints and determine what plan the user purchased, upgraded, downgraded or cancelled.Refer to Xero's documenation to learn more about setting up and receiving webhooks.https://developer.xero.com/documentation/guides/webhooks/overview/API ClientsYou can access the different API sets and their available methods through the following:accounting_api=AccountingApi(api_client)read_accounts=accounting_api.get_accounts(xero_tenant_id)asset_api=AssetApi(api_client)read_assets=asset_api.get_assets(xero_tenant_id)# ... all the API sets follow the same patternHelper MethodsOnce you have a valid Token Set in your datastore, the next time you want to call the Xero API simply initialize a newclientand refresh the token set.# configure xero-python sdk clientapi_client=ApiClient(Configuration(debug=app.config["DEBUG"],oauth2_token=OAuth2Token(client_id=app.config["CLIENT_ID"],client_secret=app.config["CLIENT_SECRET"]),),pool_threads=1,)# configure token persistence and exchange point between app session and xero-python@api_client.oauth2_token_getterdefobtain_xero_oauth2_token():returnsession.get("token")@api_client.oauth2_token_saverdefstore_xero_oauth2_token(token):session["token"]=tokensession.modified=True# get existing token settoken_set=get_token_set_from_database(user_id);//examplefunctionname# set token set to the api clientstore_xero_oauth2_token(token_set)# refresh token set on the api clientapi_client.refresh_oauth2_token()# call the Xero APIaccounting_api=AccountingApi(api_client)read_accounts=accounting_api.get_accounts(xero_tenant_id)A full list of the SDK client's methods:methoddescriptionparamsreturnsapi_client.oauth2_token_saverA decorator to register a callback function for saving refreshed token while the old token has expiredtoken_saver: the callback function acceptingtokenargumenttoken_saver to allow this method be used as decoratorapi_client.oauth2_token_getterA decorator to register a callback function for getting oauth2 tokentoken_getter: the callback function returning oauth2 token dictionarytoken_getter to allow this method be used as decoratorapi_client.revoke_oauth2_tokenRevokes a users refresh token and removes all their connections to your appN/Aempty OAuth2 tokenapi_client.refresh_oauth2_tokenRefreshes OAuth2 token setN/Anew token setapi_client.set_oauth2_tokenSets OAuth2 token directly on the clientdict token: standard token dictionaryN/Aapi_client.get_oauth2_tokenGet OAuth2 token dictionaryN/AdictUsage ExamplesAccounting APIfromxero_python.accountingimportAccountingApifromxero_python.utilsimportgetvalueaccounting_api=AccountingApi(api_client)# Get Accountsread_accounts=accounting_api.get_accounts(xero_tenant_id)account_id=getvalue(read_accounts,"accounts.0.account_id","")# Get Account by IDread_one_account=accounting_api.get_account(xero_tenant_id,account_id)# Create Invoice# get contactread_contacts=accounting_api.get_contacts(xero_tenant_id)contact_id=getvalue(read_contacts,"contacts.0.contact_id","")# get accountwhere="Type==\"SALES\"&&Status==\"ACTIVE\""read_accounts=accounting_api.get_accounts(xero_tenant_id,where=where)account_id=getvalue(read_accounts,"accounts.0.account_id","")# build Invoicescontact=Contact(contact_id=contact_id)line_item=LineItem(account_code=account_id,description="Consulting",quantity=1.0,unit_amount=10.0,)invoice=Invoice(line_items=[line_item],contact=contact,due_date=dateutil.parser.parse("2020-09-03T00:00:00Z"),date=dateutil.parser.parse("2020-07-03T00:00:00Z"),type="ACCREC")invoices=Invoices(invoices=[invoice])created_invoices=accounting_api.create_invoices(xero_tenant_id,invoices=invoices)invoice_id=getvalue(read_invoices,"invoices.0.invoice_id","")# Create Attachmentinclude_online=Truefile_name="helo-heros.jpg"path_to_upload=Path(__file__).resolve().parent.joinpath(file_name)open_file=open(path_to_upload,'rb')body=open_file.read()content_type=mimetypes.MimeTypes().guess_type(file_name)[0]created_invoice_attachments_by_file_name=accounting_api.create_invoice_attachment_by_file_name(xero_tenant_id,invoice_id,file_name,body,include_online,)SDK conventionsQuerying & FilteringDescribe the support for query options and filtering# configure api_client for use with xero-python sdk clientapi_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")defaccounting_get_invoices():api_instance=AccountingApi(api_client)xero_tenant_id='YOUR_XERO_TENANT_ID'if_modified_since=dateutil.parser.parse("2020-02-06T12:17:43.202-08:00")where='Status=="DRAFT"'order='InvoiceNumber ASC'ids=["00000000-0000-0000-0000-000000000000"]invoice_numbers=["INV-001","INV-002"]contact_ids=["00000000-0000-0000-0000-000000000000"]statuses=["DRAFT","SUBMITTED"]include_archived='true'created_by_my_app='false'summary_only='true'api_response=api_instance.get_invoices(xero_tenant_id,if_modified_since,where,order,ids,invoice_numbers,contact_ids,statuses,page,include_archived,created_by_my_app,unitdp,summary_only)Participating in Xero’s developer communityThis SDK is one of a number of SDK’s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes.Here are a few things you should be aware of as a contributor:Xero has adopted the Contributor CovenantCode of Conduct, we expect all contributors in our community to adhere to itIf you raise an issue then please make sure to fill out the Github issue template, doing so helps us help youYou’re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your codeWe have acontribution guidefor you to follow when contributing to this SDKCurious about how we generate our SDK’s? Have aread of our processand have a look at ourOpenAPISpecThis software is published under theMIT LicenseFor questions that aren’t related to SDKs please refer to ourdeveloper support page.ContributingPRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based onXero's OpenAPI specs- PR's will be evaluated and pre-merge will be incorporated into the root generation templates.VersioningWe do our best to keep OS industrysemverstandards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know.History0.1.0 (2020-03-02)First release on PyPI.