Skip to main content

Exact Online REST API Library in Python

Project description

Exact Online provides accounting software in a Software-as-a-Service delivery model. It implements an API through a REST interface. This library aims to ease its use.

Quick jump

Usage by example

Set up the basics:

from exactonline.api import ExactApi
from exactonline.exceptions import ObjectDoesNotExist
from import IniStorage

# Create a function to get the api with your own storage backend.
def get_api():
    storage = IniStorage('/path/to/config.ini')
    return ExactApi(storage=storage)
api = get_api()

Get an invoice:

# Get an invoice by your own invoice number (YourRef).
# Returns a dictionary, or raises ObjectDoesNotExist.
invoice = api.invoices.get(invoice_number='F0005555')

It looks somewhat like this:

invoice == {
    u'AmountDC': 50.8,
    u'AmountFC': 50.8,
# ...
    u'SalesEntryLines': [
        {u'AmountDC': 41.98,
         u'AmountFC': 41.98,
# ...
         u'Description': u'Omzet backups',
         u'VATBaseAmountDC': 41.98,
         u'VATBaseAmountFC': 41.98},
# ...
    u'VATAmountDC': 8.82,
    u'VATAmountFC': 8.82,
    u'YourRef': u'F0005555',
    u'__metadata': {u'type': u'Exact.Web.Api.Models.SalesEntry',
                    u'uri': u""},

Get relations:

relations_limit_2 = api.relations.filter(top=2)
# that was cheaper than: api.relations.all()[0:2]

relations_limit_2 == [
    {u'Code': u'              1068',
     u'ID': u'11111111-2222-3333-4444-555555555555',
     u'Name': u'ACME Corporation',
     u'__metadata': {u'type': u'Exact.Web.Api.Models.Account',
                     u'uri': u"')"}},
    {u'Code': u'               555',
     u'ID': u'22222222-3333-4444-5555-666666666666',
     u'Name': u'Daffy Duck Ltd.',
     u'__metadata': {u'type': u'Exact.Web.Api.Models.Account',
                     u'uri': u"')"}}

Update a relation:

daffy_duck = api.relations.get(relation_code='555')
api.relations.update(daffy_duck['ID'], {'Name': 'Daffy Duck and sons'})

Delete a relation:

daffy_duck = api.relations.get(relation_code='555')

Create an invoice:

customer_data = api.relations.get(relation_code='123')  # local relation_code
customer_guid = customer_data['ID']
invoice_data = {
    'AmountDC': str(amount_with_vat),  # DC = default currency
    'AmountFC': str(amount_with_vat),  # FC = foreign currency
    'EntryDate': invoice_date.strftime('%Y-%m-%dT%H:%M:%SZ'),  # pretend we're in UTC
    'Customer': customer_guid,
    'Description': u'Invoice description',
    'Journal': remote_journal,  # 70 "Verkoopboek"
    'ReportingPeriod': invoice_date.month,
    'ReportingYear': invoice_date.year,
    'SalesEntryLines': [],
    'VATAmountDC': str(vat_amount),
    'VATAmountFC': str(vat_amount),
    'YourRef': local_invoice_number,
    # must start uniquely at the start of a year, defaults to:
    # YYJJ0001 where YY=invoice_date.year, and JJ=remote_journal
    'InvoiceNumber': '%d%d%04d' % (invoice_date.year, remote_journal,
# The SalesEntryLines need to be filled with a bunch of dictionaries
# with these keys: AmountDC, AmountFC, Description, GLAccount,
# VATCode where GLAccount holds the Journal remote GUID, and the
# amounts are without VAT.


You may need to play around a bit to find out which fields are mandatory, and what kind of values the fields need. The Exact Online REST resources list isn’t always clear on that.

Implemented resources

View exactonline/api/ to see which resource helpers are implemented.

Currently, it looks like this:

invoices = Invoices.as_property()
ledgeraccounts = LedgerAccounts.as_property()
receivables = Receivables.as_property()
relations = Relations.as_property()

But you can call resources which don’t have a helper directly. The following two three are equivalent:

api.restv1('GET', 'crm/Accounts')'GET', 'v1/%d/crm/Accounts' % selected_division)

As are the following three:

api.restv1('GET', 'crm/Accounts?$top=2')'GET', 'v1/%d/crm/Accounts?$top=2' % selected_division)

And these:

api.invoices.filter(filter="EntryDate gt datetime'2015-01-01'")
api.restv1('GET', 'salesentry/SalesEntries?' +
  '$filter=EntryDate%20gt%20datetime%272015-01-01%27')'GET', 'v1/%d/salesentry/SalesEntries?' +
  '$filter=EntryDate%%20gt%%20datetime%%272015-01-01%%27' %
# convinced yet that the helpers are useful?

See the Exact Online REST resources list for all available resources.

Other benefits

The ExactApi class ensures that:

  • Tokens are refreshed as needed (see: exactonline/api/

  • Paginated lists are automatically downloaded in full (see: exactonline/api/


Exact Online REST API Library in Python is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 3 or any later version.


  • Replace base_url with response_url?

  • Add travis build stuff.

Further reading


  • v0.1.3:

    • Add receivables manager to the API. This manager allows you to build a list similar to the Outstanding Receivables page of Financial Reporting.

    • Add api.invoices.map_exact2foreign_invoice_numbers and api.invoices.map_foreign2exact_invoice_numbers methods to quickly get a mapping between our own and the ExactOnline invoice numbers.

    • Python3 compatibility.

    • Minor fixes.

Project details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

exactonline-0.1.3.tar.gz (20.3 kB view hashes)

Uploaded source

Supported by

AWS AWS Cloud computing Datadog Datadog Monitoring Facebook / Instagram Facebook / Instagram PSF Sponsor Fastly Fastly CDN Google Google Object Storage and Download Analytics Huawei Huawei PSF Sponsor Microsoft Microsoft PSF Sponsor NVIDIA NVIDIA PSF Sponsor Pingdom Pingdom Monitoring Salesforce Salesforce PSF Sponsor Sentry Sentry Error logging StatusPage StatusPage Status page