Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
ibanity
: Peppol access via Ibanity¶
The lino_xl.lib.ibanity
plugin adds functionality for accessing the
Peppol network using the Flowin e-invoicing API by
Ibanity. The plugin is under development, contact us to get latest
information on the progress.
For general introduction to Peppol, see Lino and Peppol (eInvoicing).
Note
Code snippets in this document (lines starting with >>>
) get tested
as part of our development workflow. The following
initialization snippet tells you which demo project is being used.
>>> from lino_book.projects.noi1r.startup import *
The tests in this document are skipped unless you have Ibanity credentials installed. See How to set up your credentials for details.
>>> if dd.plugins.ibanity.credentials is None:
... pytest.skip('this doctest requires Ibanity credentials')
Usage scenarios¶
The ibanity
plugin has two usage scenarios called “supplier management” and
“document management”. Both scenarios can be combined on a single Lino
site : a Lino hosting provider who is a customer of Ibanity can use
their certificate also for their own accounting.
Supplier management: Register other companies as Ibanity end users.
This scenario requires the site operator to have a contract with Ibanity who acts as Access Point. To activate this scenario, set the
with_suppliers
plugin setting to True.Document management: Send and receive your invoices and credit notes via the Peppol network.
To activate this scenario, set the
supplier_id
plugin setting to the supplier id you received from your Ibanity hosting provider who registered you as Ibanity end user.
This document shows things that are used in both usage scenarios. For more specific documentation about each usage scenario, see ibanity in Noi and ibanity (Ibanity in Così).
Ibanity jargon¶
- Ibanity¶
A Peppol Access Point provider for software developers who access the Peppol network via an API.
See https://ibanity.com/company
Ibanity is a solution of Isabel Group in Brussels.
- Ibanity end user¶
An organization that receives and sends their invoices and credit notes via a software that uses the Ibanity API to access the Peppol network.
- Ibanity supplier¶
Jargon synonym for Ibanity end user used by the Ibanity API for historical reasons.
- Ibanity client¶
An organization that is a certified Ibanity customer and who can register their own customers as suppliers.
- Ibanity API¶
The public Application Programmers Interface provided by Ibanity to their clients.
- Ibanity developer portal¶
The web interface where an Ibanity client gets their credentials.
- Ibanity credentials¶
A set of files with security keys to identify an Ibanity client when accessing the Ibanity API.
How to set up your credentials¶
If you want to play yourself with the Ibanity API, or if you want to configure a production site, you need to set up your credential files so that Lino can access the Ibanity API. Here is how to do this.
We assume that you have your Lino developer environment installed. Go to your copy of the cosi1 demo project and create a subdirectory named
secrets
:$ go cosi1 $ mkdir secrets
Note that directories named
secrets
are ignored by Git because they are listed in the .gitignore file.Create an account on the Ibanity developer portal, create a sandbox application, activate the “Flowin e-invoicing” product, generate a certificate, extract the certificate files and store them into the
secrets
subdirectory. You also need to decrypt the private key because Python’s requests module doesn’t support encrypted keys:$ go cosi1 $ cd secrets $ openssl rsa -in private_key.pem -out decrypted_private_key.pem
Still in the
secrets
directory, create a file namedcredentials.txt
that contains client_id and your client_secret. The file must contain a single line of text in the format{client_id}:{client_secret}
.
Now you should be able to test this document by saying:
$ go book
$ doctest docs/plugins/ibanity.rst
Exploring the Ibanity API¶
This section explores the Ibanity API, also known as the the Flowin e-invoicing Services.
Access token¶
The ibanity.credentials
setting is the identifier of your
application in the Ibanity developer portal.
This is a string of the form “{client_id}:{client_secret}”.
>>> dd.plugins.ibanity.credentials
'a38f6dd1-7b48-4dbc-8493-2a142b6e134a:valid_client_secret'
>>> dd.plugins.ibanity.cert_file
PosixPath('.../secrets/certificate.pem')
>>> print(dd.plugins.ibanity.cert_file.read_text().strip())
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
>>> ses = dd.plugins.ibanity.get_ibanity_session()
>>> pprint(ses.get_access_token())
{'access_token': '...',
'expires_in': 300,
'not-before-policy': 0,
'refresh_expires_in': 300,
'refresh_token': '...',
'scope': 'email profile',
'session_state': '94bdf4ce-0dea-4ab7-abda-a53c7ba4bc87',
'token_type': 'bearer'}
The above code snippet is a confirmation that your credentials are set up correctly. Congratulations! You won’t need this access token directly, but Lino will call it internally for every API call.
Suppliers¶
A supplier in Ibanity is the one sending and/or receiving an invoice via Peppol. Those are the customers of the hosting provider. Internally the Ibanity team uses the term “end user” instead of “supplier” and might change that at some point on the API too. An end user is the one using your software.
>>> list_suppliers = ses.list_suppliers()
>>> pprint(list_suppliers)
{'data': [{'attributes': {'city': 'Leuven',
'companyNumber': '1234567890',
'contactEmail': 'contact@example.be',
'country': 'Belgium',
'createdAt': '...',
'email': 'someone@example.be',
'enterpriseIdentification': {'enterpriseNumber': '1234567890',
'vatNumber': 'BE1234567890'},
'homepage': 'www.home.com',
'ibans': [{'id': 'bdfa52c6-2b50-4690-8b8d-24541a92c578',
'value': 'BE68539007547034'},
{'id': 'dcb9f7c2-be2c-4b52-8d77-3ed2bc05c5f8',
'value': 'BE68539007547034'}],
'names': [{'id': '3dffba33-97af-4477-9fab-2d9d2dc31cee',
'value': 'Company'},
{'id': '99e410cc-d6f0-4f36-8096-949741ea8ec3',
'value': 'Company S.A.'}],
'onboardingStatus': 'CREATED',
'peppolReceiver': False,
'phoneNumber': '+3254321121',
'street': 'Street',
'streetNumber': '2',
'supportEmail': 'support@example.be',
'supportPhone': '+3212345121',
'supportUrl': 'www.support.com',
'zip': '3000'},
'id': '273c1bdf-6258-4484-b6fb-74363721d51f',
'type': 'supplier'}],
'meta': {'paging': {'number': 0, 'size': 2000, 'total': 1}}}
>>> for sup in list_suppliers['data']:
... createdAt1 = sup['attributes'].pop('createdAt')
... vat_id = sup['attributes']['enterpriseIdentification']['vatNumber']
... sup_info = ses.get_supplier(sup['id'])
... createdAt2 = sup_info['data']['attributes'].pop('createdAt')
... assert sup_info['data']['attributes'] == sup['attributes']
... print(f"Supplier {sup['id']} has VAT id {vat_id}")
Supplier 273c1bdf-6258-4484-b6fb-74363721d51f has VAT id BE1234567890
>>> from lino_xl.lib.ibanity.utils import DEMO_SUPPLIER_ID
>>> DEMO_SUPPLIER_ID
'273c1bdf-6258-4484-b6fb-74363721d51f'
>>> pprint(ses.get_supplier(DEMO_SUPPLIER_ID))
{'data': {'attributes': {'city': 'Leuven',
'companyNumber': '1234567890',
'contactEmail': 'contact@example.be',
'country': 'Belgium',
'createdAt': '...',
'email': 'someone@example.be',
'enterpriseIdentification': {'enterpriseNumber': '1234567890',
'vatNumber': 'BE1234567890'},
'homepage': 'www.home.com',
'ibans': [{'id': 'bdfa52c6-2b50-4690-8b8d-24541a92c578',
'value': 'BE68539007547034'},
{'id': 'dcb9f7c2-be2c-4b52-8d77-3ed2bc05c5f8',
'value': 'BE68539007547034'}],
'names': [{'id': '3dffba33-97af-4477-9fab-2d9d2dc31cee',
'value': 'Company'},
{'id': '99e410cc-d6f0-4f36-8096-949741ea8ec3',
'value': 'Company S.A.'}],
'onboardingStatus': 'CREATED',
'peppolReceiver': False,
'phoneNumber': '+3254321121',
'street': 'Street',
'streetNumber': '2',
'supportEmail': 'support@example.be',
'supportPhone': '+3212345121',
'supportUrl': 'www.support.com',
'zip': '3000'},
'id': '273c1bdf-6258-4484-b6fb-74363721d51f',
'type': 'supplier'}}
Let’s try to register a new supplier.
>>> rv = ses.create_supplier()
Traceback (most recent call last):
...
Exception: post https://api.ibanity.com/einvoicing/suppliers/ returned unexpected status code 500
That’s normal because we must supply the data.
>>> d = {}
>>> d["enterpriseIdentification"] = {
... "enterpriseNumber": "1234567890",
... "vatNumber": "BE1234567890"}
>>> d["contactEmail"] = "contact@example.be"
>>> d["ibans"] = [{"value": "BE68539007547034"}, {"value": "BE68539007547034"}]
>>> d["names"] = [{"value": "Company" }, {"value": "Company S.A."}]
>>> d["city"] = "Eupen"
>>> d["country"] = "Belgium"
>>> d["email"] = "someone@example.com"
>>> d["homepage"] = "www.home.com"
>>> d["phoneNumber"] = "+3287654312"
>>> d["street"] = "Neustraße"
>>> d["streetNumber"] = "123"
>>> d["supportEmail"] = "support@example.be"
>>> d["supportPhone"] = "+3212345121"
>>> d["supportUrl"] = "www.support.com"
>>> d["zip"] = "4700"
>>> d["peppolReceiver"] = True
>>> rv = ses.create_supplier(**d)
>>> pprint(rv)
{'data': {'attributes': {'city': 'Eupen',
'contactEmail': 'contact@example.be',
'country': 'Belgium',
'createdAt': '...',
'email': 'someone@example.com',
'enterpriseIdentification': {'enterpriseNumber': '1234567890',
'vatNumber': 'BE1234567890'},
'homepage': 'www.home.com',
'ibans': [{'id': '...', 'value': 'BE68539007547034'},
{'id': '...', 'value': 'BE68539007547034'}],
'names': [{'id': '...', 'value': 'Company'},
{'id': '...', 'value': 'Company S.A.'}],
'onboardingStatus': 'CREATED',
'peppolReceiver': True,
'phoneNumber': '+3287654312',
'street': 'Neustraße',
'streetNumber': '123',
'supportEmail': 'support@example.be',
'supportPhone': '+3212345121',
'supportUrl': 'www.support.com',
'zip': '4700'},
'id': '...',
'type': 'supplier'}}
>>> rv['data']['type']
'supplier'
The supplier_id of the newly created supplier is here:
>>> new_supplier_id = rv['data']['id']
We don’t test its value here because this value changes each time. But we might use it for retrieving the onboarding status of our new supplier.
>>> rv = ses.get_supplier(new_supplier_id)
>>> assert rv['data']['id'] == new_supplier_id
Unfortunately the Ibanity sandbox doesn’t actually store the new supplier; if we try to get it, we will still just get their plain old demo supplier:
>>> pprint(rv['data']['attributes']['city'])
'Leuven'
>>> pprint(rv['data']['attributes']['names'])
[{'id': '3dffba33-97af-4477-9fab-2d9d2dc31cee', 'value': 'Company'},
{'id': '99e410cc-d6f0-4f36-8096-949741ea8ec3', 'value': 'Company S.A.'}]
Registrations¶
A registration is when an supplier has registered with an Access Point. The List Peppol Registrations returns a list of registrations for a given supplier.
>>> pprint(ses.list_registrations(DEMO_SUPPLIER_ID))
{'data': [{'attributes': {'accessPoints': ['Billit'],
'createdAt': '2023-08-16T12:38:16.662354Z',
'failedSince': '2023-08-16T12:38:16.662354Z',
'modifiedAt': '2023-08-16T12:38:22.575373Z',
'reason': 'already-registered',
'status': 'registration-failed',
'type': 'enterprise-number',
'value': '0143824670'},
'id': '9d12d39d-2b03-4ea6-a770-f5d6b37edea7',
'type': 'peppolRegistration'}]}
This API call requires a supplierId argument, but at least in the sandbox it then ignores this argument:
>>> reg1 = ses.list_registrations("123")
>>> reg2 = ses.list_registrations("456")
>>> reg1 == reg2
True
Documents¶
When talking to the Ibanity API about your invoices and other business documents, you first need to differentiate between “inbound” and “outbound” documents. Compare this to an email client where you have an “inbox” and an “outbox”.
Workflow for outbound documents:
Customer search 🡒 Customer reachability status (Document formats supported by this customer)
Send document 🡒 Receipt
Get feedback (ask status of an outbound document). The status is either successful, in which case we receive a transmissionID, or unsuccessful in which case we receive more details on the reason why it failed.
Workflow for inbound documents:
List suppliers 🡒 list of suppliers
List Peppol inbound documents (1 request per supplier) 🡒
Get Peppol inbound document (1 request per document) 🡒
Some properties are common to both inbound and outbound documents:
attributes.createdAt
: when the document entered the Peppol networkrelationships.supplier
: the Peppol end point who posted this document into the Peppol network. This has nothing to do with the supplier on the invoice (who is called the seller)id
: unique identifier of this document.attributes.transmissionId
: an additional unique identifier within the Peppol network. In case of an issue this can be used in communication with the sending party.the body of the document in UBL format
Outbound documents have three additional properties:
status
: one of {created, sending, sent, invalid, send-error}errors
: one of {invalid-malicious, invalid-format, invalid-xsd, invalid-schematron, invalid-size, invalid-type, error-customer-not-registered, error-document-type-not-supported, error-customer-access-point-issue}type
: one of {“peppolInvoice”, “peppolOutboundDocument”, “peppolOutboundInvoice”, “peppolOutboundCreditNote”}
When asking for a list of documents, you specify a time range. For outbound documents this range means when their status changed (fromStatusChanged and toStatusChanged) while for inbound documents it means their creation time
You cannot create (post) an incoming document.
Outbound documents¶
>>> ar = rt.login("robin")
>>> qs = trading.VatProductInvoice.objects.filter(journal__ref="SLS")
>>> obj = qs.order_by("accounting_period__year", "number").last()
>>> obj
VatProductInvoice #106 ('SLS 10/2015')
>>> xmlfile, url = obj.make_xml_file(ar)
Make ...lino_book/projects/noi1e/settings/media/xml/2015/SLS-106.xml from SLS 10/2015 ...
Validate SLS-106.xml against .../lino_xl/lib/vat/XSD/PEPPOL-EN16931-UBL.sch ...
>>> res = ses.create_outbound_document(DEMO_SUPPLIER_ID, xmlfile)
>>> pprint(res)
{'data': {'attributes': {'createdAt': '2019-07-17T07:31:30.763402Z',
'status': 'created'},
'id': '94884e80-cc4a-4583-bd4a-288095c7876f',
'relationships': {'supplier': {'data': {'id': '273c1bdf-6258-4484-b6fb-74363721d51f',
'type': 'supplier'}}},
'type': 'peppolInvoice'}}
>>> assert res['data']['attributes']['status'] == "created"
>>> assert res['data']['relationships']['supplier']['data']['id'] == DEMO_SUPPLIER_ID
>>> doc_id = res['data']['id']
The sandbox neither validates nor actually creates my document, it always returns a same document id. I can ask it to send another invoice and the sandbox will assign it the same doc_id as before.
>>> obj = trading.VatProductInvoice.objects.get(pk=105)
>>> xmlfile, url = obj.make_xml_file(ar)
Make ...lino_book/projects/noi1e/settings/media/xml/2015/SLS-105.xml from SLS 9/2015 ...
Validate SLS-105.xml against .../lino_xl/lib/vat/XSD/PEPPOL-EN16931-UBL.sch ...
>>> res = ses.create_outbound_document(DEMO_SUPPLIER_ID, xmlfile)
>>> assert res['data']['id'] == doc_id
>>> pprint(res)
{'data': {'attributes': {'createdAt': '2019-07-17T07:31:30.763402Z',
'status': 'created'},
'id': '94884e80-cc4a-4583-bd4a-288095c7876f',
'relationships': {'supplier': {'data': {'id': '273c1bdf-6258-4484-b6fb-74363721d51f',
'type': 'supplier'}}},
'type': 'peppolInvoice'}}
>>> res = ses.get_outbound_document(DEMO_SUPPLIER_ID, doc_id)
>>> pprint(res)
{'data': {'attributes': {'createdAt': '2019-07-17T07:31:30.763402Z',
'errors': None,
'status': 'sent',
'transmissionId': 'ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5'},
'id': '94884e80-cc4a-4583-bd4a-288095c7876f',
'relationships': {'supplier': {'data': {'id': '273c1bdf-6258-4484-b6fb-74363721d51f',
'type': 'supplier'}}},
'type': 'peppolInvoice'}}
>>> assert res['data']['id'] == doc_id
Also here, the sandbox “cheats”, it doesn’t check whether the requested document actually exists, it just returns a document descriptor for the requested id and type.
>>> res = ses.get_outbound_document(DEMO_SUPPLIER_ID, doc_id, credit_note=True)
>>> pprint(res)
{'data': {'attributes': {'createdAt': '2019-07-17T07:31:39.211221Z',
'errors': None,
'status': 'created',
'transmissionId': 'ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5'},
'id': '94884e80-cc4a-4583-bd4a-288095c7876f',
'relationships': {'supplier': {'data': {'id': '273c1bdf-6258-4484-b6fb-74363721d51f',
'type': 'supplier'}}},
'type': 'peppolCreditNote'}}
>>> assert res['data']['id'] == doc_id
>>> doc_id = '12345678-1234-5678-bd4a-1234567890123'
>>> res = ses.get_outbound_document(DEMO_SUPPLIER_ID, doc_id, credit_note=True)
>>> res['data']['id']
'12345678-1234-5678-bd4a-1234567890123'
>>> start_time = datetime.datetime(2020,7,31,0,0,0)
>>> print(start_time.isoformat())
2020-07-31T00:00:00
>>> res = ses.list_outbound_documents(DEMO_SUPPLIER_ID, start_time)
>>> pprint(res)
{'data': [{'attributes': {'createdAt': '2020-07-31T00:00:00',
'status': 'created'},
'id': '94884e80-cc4a-4583-bd4a-288095c7876f',
'relationships': {'supplier': {'data': {'id': 'de142988-373c-4829-8181-92bdaf8ef26d',
'type': 'supplier'}}},
'type': 'peppolInvoice'}],
'meta': {'paging': {'number': 0, 'size': 2000, 'total': 1}}}
Inbound documents¶
>>> res = ses.list_inbound_documents(DEMO_SUPPLIER_ID)
>>> sorted(res.keys())
['data', 'meta']
>>> pprint(res['data'])
[{'attributes': {'createdAt': '...',
'transmissionId': 'c038dbdc1-26ed-41bf-9ebf-37g3c4ceaa58'},
'id': '431cb851-5bb2-4526-8149-5655d648292f',
'relationships': {'supplier': {'data': {'id': 'de142988-373c-4829-8181-92bdaf8ef26d',
'type': 'supplier'}}},
'type': 'peppolInboundDocument'}]
>>> doc_info = res['data'][0]
>>> doc_id = doc_info['id']
>>> res = ses.get_inbound_document_xml(doc_id)
>>> pprint(res)
('<?xml version="1.0" encoding="UTF-8"?>\n'
' <Invoice '
'xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"\n'
' '
'xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"\n'
' xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">\n'
' </Invoice>')
Customer search¶
This is used to check whether my customer exists.
Belgian participants are registered with the Belgian company number, for which identifier 0208 can be used. Optionally, the customer can be registered with their VAT number, for which identifier 9925 can be used.
>>> eas = "9925"
>>> vat_id = "BE0010012671"
>>> res = ses.customer_search(f"{eas}:{vat_id}")
>>> pprint(res)
{'data': {'attributes': {'customerReference': '9925:BE0010012671',
'supportedDocumentFormats': [{'customizationId': 'urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0',
'localName': 'Invoice',
'profileId': 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
'rootNamespace': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
'ublVersionId': '2.1'},
{'customizationId': 'urn:cen.eu:en16931:2017#conformant#urn:UBL.BE:1.0.0.20180214',
'localName': 'Invoice',
'profileId': 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
'rootNamespace': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
'ublVersionId': '2.1'},
{'customizationId': 'urn:cen.eu:en16931:2017#conformant#urn:UBL.BE:1.0.0.20180214',
'localName': 'CreditNote',
'profileId': 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
'rootNamespace': 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2',
'ublVersionId': '2.1'},
{'customizationId': 'urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0',
'localName': 'CreditNote',
'profileId': 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
'rootNamespace': 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2',
'ublVersionId': '2.1'},
{'customizationId': 'urn:cen.eu:en16931:2017#compliant#urn:fdc:nen.nl:nlcius:v1.0',
'localName': 'Invoice',
'profileId': 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
'rootNamespace': 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
'ublVersionId': '2.1'},
{'customizationId': 'urn:cen.eu:en16931:2017#compliant#urn:fdc:nen.nl:nlcius:v1.0',
'localName': 'CreditNote',
'profileId': 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
'rootNamespace': 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2',
'ublVersionId': '2.1'}]},
'id': '99988e77-cc4a-4583-bd4a-288095c5566e',
'type': 'peppolCustomerSearch'}}
The Flowin sandbox contains hard-coded fake data. Using another reference than
'9925:BE0010012671'
as customerReference will in result a 404 response
even when you specify a valid VAT number:
>>> res = ses.customer_search(f"9925:BE0433670865")
Traceback (most recent call last):
...
Exception: post https://api.ibanity.com/einvoicing/peppol/customer-searches returned unexpected status code 404
Class reference¶
The ibanity plugin injects a checkbox field “is_outbound” into
contacts.Journal
and into contacts.Partner
. In the demo data
this field is checked (1) for journal SLS and (2) for some partners (a subset of
those with a vat_id).
- class lino_xl.lib.contacts.Partner¶
- Noindex:
- is_outbound¶
Whether sales invoices and creditnoutes to this partner should be sent via the Peppol network.
- class lino_xl.lib.ibanity.OnboardingStates¶
A choicelist with values for the
Supplier.onboarding_state
.
- class lino_xl.lib.ibanity.Supplier¶
Django model used to represent an Ibanity supplier
- class lino_xl.lib.ibanity.CustomerStates¶
A choicelist with three values:
active: The customer is already actively using Peppol and wants to receive all his invoices via Peppol, including your documents.
potential: The customer can be reached on Peppol, but did not yet confirm to receive your documents via Peppol.
unreachable: The customer is not on Peppol, for example because his bank application is not connected to Peppol yet.
- class lino_xl.lib.ibanity.SuppliersListChecker¶
Checks whether all suppliers that were registered by this Ibanity client have a
Supplier
row on this Lino site.
- class lino_xl.lib.ibanity.SupplierChecker¶
Synchronizes this
Supplier
row with the data registered in the Ibanity API.
Note that SuppliersListChecker
and SupplierChecker
are
manual checkers. We do not want these checkers to run automatically during
checkdata
, only when called manually, because it requires Ibanity
credentials, which are not available e.g. on GitLab.
Plugin settings¶
- lino_xl.lib.ibanity.supplier_id¶
The identification code of this Lino site as an Ibanity supplier.
- lino_xl.lib.ibanity.with_suppliers¶
Whether this site can register other organizations as Ibanity suppliers.
The following three settings are filled automatically by the plugin at startup:
- lino_xl.lib.ibanity.credentials¶
The Ibanity credentials to use on this Lino site for accessing the Ibanity API.
Must be specified in the form
"{client_id}:{client_secret}"
.