Versioning
The version is in the path — https://api.essentio.pro/v1 — and it is the only one there is. What v1 answers is a promise: within v1 the API only grows, so a program written against it today keeps working.
v1 only grows
Within v1 these may be added:
- endpoints, and optional parameters and fields of a request;
- fields of an answer;
- values of a field that lists its values — a document’s
status, a Line’svat_category, a Payment’spayment_method; - error types, Refusal codes and remedies;
- Webhook events.
Nothing is renamed or removed: no endpoint, field, value, error type, Refusal code or event. There are no versions pinned by date, and nothing to send to choose one.
So write a program that expects more than it knows:
- Ignore a field you do not know. Never refuse an answer because it holds one — don’t validate answers against a closed schema.
- Keep a value you do not know rather than failing on it, and treat it as “something else”: an unknown
statusis a state your program does not handle yet. - Treat an unknown error type by its HTTP status, and an unknown Refusal code as a refusal you do not handle yet, showing its
message. - Ignore a Webhook event whose
typeyou do not know, and answer it2xx.
Open value sets
The contract, the OpenAPI file, is written to be read that way. It closes no object — an answer may carry fields the file you downloaded does not list — and it lists an answer’s values as they are today under x-known-values, never as an enum, with each description saying: “Within v1 another value may be added: keep a value you do not know.” A client generated from the file therefore takes a new value as a string instead of failing to parse the answer.
What you send is held to the rules: a request’s parameters and fields keep their enums, and a value the API does not take is refused with a 422 invalid_request.
The API is tested against the file: every answer the test suite receives is checked against it, and a field the server sends that the file does not declare fails the build. Download the file again to see what was added.
A breaking change is a v2
A change that could break a program written against v1 — a field renamed, removed or given another type, a value no longer answered — is never made to v1. It is made in a v2, at https://api.essentio.pro/v2, and v1 keeps answering beside it for at least twelve months after v2 is announced, so you move when it suits you. There is no v2 today.
Changelog
- 2026-10-03 — a Client’s VAT number is written with its country code. A document’s EN16931 XML to a Client whose VAT number Essentio stores without its country code — as the Client form stores every one — was refused
not_exportablesince 2026-10-02, saying the number does not start with its country code; it is now exported, the number written as the Client’s VAT identifier (BT-48) with the Client’s country first, in capitals (ELfor Greece). A number stored with two letters first is written as stored, as before. Still refused: a Client’s number whose two first letters are not a country code in capitals, or one stored without a code for a Client with no country, in the new words “The Client’s VAT Number has no country code the export can use: enter the number with the Client’s country set, or with its code first in capitals, such as DE123456789. Fix it on the Client.” The JSON answers are unchanged. - 2026-10-02 — the EN 16931 check asks for what the standard asks for. A document’s EN16931 XML is refused
not_exportable(the same code, in new words) by the standard’s own rule table: a VAT number of the Business only for an intra-community supply (K) or an export (G); forS,Z,EandAELines a VAT number or a TAX ID; for a document ofOLines a Registration Number or a TAX ID — so a Business with a TAX ID and no VAT number, or a Non-VAT Payer, now exports where it was refused. Newly refused, before any file is written: anOLine beside another VAT category, a VAT number (the Business’s or the Client’s) not starting with its two-letter country code in capitals, anAEdocument whose Client has no VAT number, and aKdocument whose Client has no VAT number or no country.error.messagecarries each reason as one sentence saying where it is fixed. The JSON answers, and the XML of a document that passed before, are unchanged. - 2026-10-02 — a document’s XML follows EN 16931 itself. A document’s EN16931 XML no longer claims Peppol BIS Billing 3.0: its
cbc:CustomizationIDisurn:cen.eu:en16931:2017and it carries nocbc:ProfileID. A Business with no bank account is written with the payment means code1(not defined) where it was30(credit transfer) with no account. A document whose Lines are all not subject to VAT (O) carries neither party’s VAT number, and anOLine or breakdown nocbc:Percent. A Business whose VAT number is not written — a Non-VAT Payer, whose stored number the file no longer carries, or a document ofOLines — is identified by its TAX ID (BT-29, BT-32) when it has one. A document with an intra-community supply (K) carries its delivery: the issue date and the Client’s country. Any other file of a VAT payer changes in itscbc:CustomizationIDandcbc:ProfileIDonly. The JSON answers are unchanged. - 2026-10-02 — a Non-VAT Payer’s VAT number is printed nowhere. A Business set to Non-VAT Payer keeps the VAT number it stored and prints none: its documents’ From block and PDFs, the public page, Receipts and Statements leave it out, and the Business setup answers
vat_numberasnullwhile it is a Non-VAT Payer. A VAT payer’s answers are unchanged. The one place that still carries the stored number is the document’s EN16931 XML, until a later change. The field already allowednull: nothing was added. - 2026-10-02 — a Product’s price is read to its last decimal, and held to its currency. A Product’s
price— as the API answers it and to the MCP server’s tools — is the price as stored: in a currency of three decimals a price of 10.125 is"10.125", where it read"10.13", andprice_without_vatandprice_with_vatare computed from it ("10.125"and not"10.130"). A price of two decimals or fewer reads as before. Apricesent with more decimals than itscurrencyhas was stored and read rounded, and one of more than 15 digits before the point failed; each is now a422invalid_requestwitherrors.price, and nothing is written. Acurrencychanged alone to one the Product’s price has more decimals than is refused the same way, witherrors.currency. Aprice, or a Payment’samount, written with an exponent of more than three digits ("1e1000") is refused in words, a422invalid_requesttoo; it was refused later for another reason, or failed. MCP’s tools answer the same. - 2026-10-02 — a number or an amount no column can hold is refused in words. An issue, a send or a Payment whose next number is the largest the numbering holds answered a
500; it is now a422refusalwith the new codenumbering_at_its_end, nothing written — a Draft can still be issued with an unused earlier number sent as itsdocument_number. A Client’scredit_limitwith a third decimal is refused (invalid_request), where it was rounded to two. And these, which answered a500, are now a422invalid_requestnaming the field:payment_termspast 999999 days, on a Client and a document; a Product’sstock_quantityorminimum_stockpast 2147483647; a Line whose Net amount (quantity × unit price) is not below 1,000,000,000,000,000, under itsunit_price; and Lines whose Total is not below it, underlines. - 2026-10-01 — a document says when, to whom and how it was last sent. A document — as the API answers it, in a Webhook’s
dataand to the MCP server’s tools — carrieslast_sent:{at, recipients, via}, the newest time it was sent while it is marked sent, ornull.viais the provider that took the email (essentio,gmail,microsoft,smtp) ormarked, a mark by hand with norecipients. Nothing else changed: a field is only ever added within v1. - 2026-09-30 — Essentio sends a Business's mail. A Business with no mailbox connected now sends through Essentio's own mail server, so Send is open for it where it was refused
no_mail_provider; that code now means a Business with no email address of its own. The new codesending_limit_reachedrefuses a Send past the Business's daily number of recipients through Essentio, with the remedyconnect_mail_provider. - 2026-09-30 — a document’s XML carries its Lines’ notes. A Line’s
notes, downloaded as the document’s EN16931 XML, is written in that Line as BT-127, Invoice line note: onecbc:Noteafter the Line’scbc:ID, in an Invoice’scac:InvoiceLineand a Credit Note’scac:CreditNoteLine. A Line with no note exports as before, and nothing is written as the Item’s description (BT-154). - 2026-09-30 — a Line takes a note, and a Line’s errors are in plain words. A Line sent to create or update a Draft — through the API or the MCP server’s
essentio_create_draftandessentio_update_draft— takesnotes, at most 500 characters, printed with the Line on the document; until now a Line’snoteswas ignored. A document’s Lines — as the API answers them, in a Webhook’sdataand to the MCP server’s tools — carrynotes,nullwhen the Line has none. And an error about a Line now names the Line by its place, counted from 1, and its field by name: “Line 1: Unit must not be longer than 50 characters.”, where it read “The lines.0.unit field must not be greater than 50 characters.”. Every Line error has the new words, a decimal sent as a JSON number included; the keys inerrorsdid not change (lines.0.unit). - 2026-09-30 — the MCP server's answers no longer link to a plan. A document's
actions, as the MCP server's tools answer them, carry noremedy_urlwith the remedychoose_plan; its code, words and remedy stay. The Public API still sends the link, andconnect_mail_provider's link stays on both. - 2026-09-30 — a Test business keeps no real books. The Business setup and every document carry
is_test. A Test business’s numbers carryTEST-in front of what its number patterns make; its XML is refused with the new codeexports_not_in_a_test_business; and it meets the Free plan’s Issuing limit (issuing_limit_reached), which a reset clears. An ordinary Business’s answers are unchanged. - 2026-09-30 — a document says whether it is Overdue, and names its conversion. A document — as the API answers it, in a Webhook’s
dataand to the MCP server’s tools — carries four more fields:is_overdueanddays_overdue(nullwhile it is not Overdue), as Essentio shows it, so an overdue Invoice no longer reads onlyissued;proforma_id, the Proforma Invoice it was converted from; andconverted_into_id, the Invoice a Proforma Invoice was converted into. Nothing else changed: a field is only ever added within v1. - 2026-09-29 —
choose_plansays where to choose a plan. A document’sactionsnow carryremedy_urlwith the remedychoose_plantoo, as they did withconnect_mail_provider: the page in Essentio where the Business’s Owner chooses a plan (Settings → Billing), for a person signed in to Essentio who belongs to the Business. - 2026-09-29 — a Draft deleted meanwhile is refused. An issue, a send or an update of a Draft that another request deleted while this one waited for it issued or changed the deleted Draft. It is now a
422refusal,draft_deleted("This draft was deleted."): nothing is written and no number is taken. A delete of a Draft that another request issued while this one waited for it deleted the issued document; it is now a422refusal,issued_not_deleted, and the document stays. A delete of a Draft that another request deleted meanwhile is answered as before, as done. - 2026-09-29 — an amendment that sends no field says there is nothing to change.
PATCH /v1/payments/{id}with a body that sends none of a Payment’s fields —{}— answered a422refusalsaying the amount must be greater than zero, a field it never sent. It is now a422invalid_requestwitherrors.payment: “Nothing to change: send at least one field.” Nothing is written, as before. MCP’sessentio_amend_paymentgiven onlypayment_idanswers the same. - 2026-09-29 — on the Free plan the Owner’s own MCP Connected app is not cut to reading. The plan cuts a Connected app as it cuts the person who authorised it: on the Free plan the Owner’s own app does what the Role it was given lets it — creating Clients, issuing Invoices up to the Issuing limit, where it is refused
issuing_limit_reached— and an Admin’s or a Member’s still only reads (plan_role_viewing). A document’sactionsanswer the same. A Paused Business and the Public API are unchanged. - 2026-09-29 — the Public API needs a paid plan. While a Business is on the Free plan or Paused, every request of its API keys and Connected apps — a read included — is a
403permissionwith the codepaid_plan_requiredand the remedychoose_plan, and does nothing. The keys and apps are not revoked: the same credential works again once the Business is on Pro or Pro Plus. The MCP server is not the Public API and keeps working on every plan, sobusiness_pausedandplan_role_viewing(the entry below) are now answered there and in Essentio, never by the Public API.messagesays whether the Business is on the Free plan or paused. - 2026-09-29 — a Business’s plan cuts what a credential may do. On the Free plan every credential only reads, and a Paused Business keeps reading and recording Payments. A change the plan does not let is still a
403permission, now with acode—business_pausedorplan_role_viewing— and the remedychoose_plan; a document’sactionsanswer the same codes. - 2026-09-29 — an update of a Draft linked to a Cancelled Proforma Invoice is refused.
PATCH /v1/documents/{id}of a Draft still linked to a Proforma Invoice that was cancelled answered200and issued the Draft. It is now a422refusal,cancelled_not_converted: nothing is written, the Draft keeps its link and takes no number. - 2026-09-29 — every refusal carries a Refusal code. Three answers that were a
422invalid_requestin words alone are now a422refusalwith acode, in the same words: an update of an issued document (issued_not_changed), an update of a Draft linked to a Proforma Invoice that does not convert into it (proforma_deleted,only_into_an_invoice,proforma_other_currency,proforma_other_client), and the XML of a document that is not exported (not_exportable).invalid_requestnow means only that the request’s fields, parameters or headers were refused. The Refusal codes list every code Essentio has, including the ones no operation answers today.