EU VAT determination for a sales ledger, 200 lines per call
One VAT determination per line — place of supply, mechanism, rate, compulsory mentions — for up to 200 transactions in a single paid call, each sourced article by article.
By Matthias Begot · · Updated
VAT is decided transaction by transaction, but it is worked on ledger by
ledger. An agent closing a month of sales, re-judging an order book, or
checking a supplier journal before it is booked needs one determination per
line — who charges the tax, under which mechanism, where it is taxed, at which
rate, and what the invoice must say. One paid call —
POST /invoice/vat-treatment/batch —
judges up to 200 transactions under Directive 2006/112/EC, each sourced
article by article, settled as one x402 payment for the whole call.
The problem: an EN 16931 validator judges the document, not the transaction
An invoice can satisfy every normative BR-* rule of EN 16931 and still be
refused, because the rules that make it wrong are not in the document schema.
An intra-EU B2B dispatch invoiced without its reverse-charge mention is
structurally perfect and substantively broken. That is the split between the
two endpoints: /invoice/validate judges the
document, /invoice/vat-treatment
judges the transaction.
The judgement is also line-local. Two rows of the same ledger, same supplier, same month, differ on one declared field — a customer VAT number known valid versus unknown — and land on opposite mechanisms: exempt with the customer self-accounting, or French VAT charged at the standard rate. There is no ledger-level verdict to compute; there are N verdicts to collect.
Why this is a 2026 problem and not a 2030 one
The regulatory clock is what turns this from a spreadsheet chore into an agent’s job.
- France: from 1 September 2026, every VAT-registered business established in France must be able to receive electronic invoices, and large and intermediate-sized enterprises must issue them; micro, small and medium enterprises follow by 1 September 2027 (impots.gouv.fr).
- European Union: the VAT in the Digital Age package — Council Directive (EU) 2025/516 of 11 March 2025, in force since 14 April 2025 — makes e-invoicing the default and imposes digital reporting of cross-border B2B supplies from 1 July 2030, with domestic reporting systems to be aligned by 1 January 2035 (European Commission).
Structured invoices in, structured reporting out. What stops being tolerable in that pipeline is a mechanism guessed by a template.
The call: one array, up to 200 transactions
The body carries a single field, items, holding 1 to 200 entries. Each entry
is exactly the body of the single call — same required fields, same closed
enumerations, same optional customer_vat_status, amount and currency.
POST /invoice/vat-treatment/batch
Content-Type: application/json
{
"items": [
{ "supplier_country": "FR", "customer_country": "IT", "transaction_type": "services", "customer_type": "business", "customer_vat_status": "valid", "supply_kind": "services_b2b_general", "date": "2026-07-15", "amount": "3200.00", "currency": "EUR" },
{ "supplier_country": "FR", "customer_country": "ES", "transaction_type": "goods", "customer_type": "business", "customer_vat_status": "unknown", "supply_kind": "intra_eu_dispatch", "date": "2026-07-15", "amount": "4000.00", "currency": "EUR" },
{ "supplier_country": "FR", "customer_country": "CH", "transaction_type": "goods", "customer_type": "business", "supply_kind": "export_outside_eu", "date": "2026-07-15", "amount": "9000.00", "currency": "EUR" }
]
}
supply_kind is never inferred: it is one of eight declared values (domestic,
intra-EU dispatch, export, import, B2B services, B2C services, land-related
services, electronic B2C services). The date of each line selects the rate
in force on that line — a July 2019 row is rated with the 2019 rate, not
today’s.
What comes back: one verdict per line, one snapshot for the ledger
The response is the standard UnifiedResponse. Four properties make it usable
as a month-close primitive:
results[]is in input order, each element carrying its ownindex, so verdicts re-pair with ledger rows without bookkeeping.- Each
verdictis the unit determination field for field:rule_id,mechanism,liable,place_of_supplywith its cited article,rate,required_invoice_mentions, pluscomputedwhen the line carried an amount andrationalewhen there is something to explain. shareddeduplicates the legal work. Over 200 lines mixing five mechanisms, the distinct sets of legal bases and caveats can be counted on one hand; they are served once, and each result points at its set throughlegal_basis_refandcaveats_ref.- One
provenancecovers the whole call —directive-2006-112 + tedb, withfreshness.as_ofdating the committed rate snapshot. Every line of the ledger was judged against the same stated state of the rates.
Here is the three-line ledger above, abridged from a real response:
{
"data": {
"count": 3,
"results": [
{ "index": 0, "status": "ok", "legal_basis_ref": 0, "caveats_ref": 0,
"verdict": { "rule_id": "INV-VAT-S02", "mechanism": "reverse_charge", "liable": "customer",
"place_of_supply": { "country": "IT", "basis": { "celex": "32006L0112", "article": "44", "title": "Place of supply of services to a taxable person: where the customer is established", "query": "/legal/eu-act?celex=32006L0112&article=44" } },
"rate": { "country": "IT", "kind": "standard", "percentage": "22" },
"required_invoice_mentions": [ { "text": "Reverse charge", "language": "en" }, { "text": "Autoliquidation", "language": "fr", "national_basis": { "jurisdiction": "FR", "reference": "CGI, art. 283-2 (mention obligatoire, art. 242 nonies A ann. II)" } } ],
"computed": { "taxable_amount": "3200.00", "rate_percentage": "0", "vat_amount": "0.00", "total_amount": "3200.00" } } },
{ "index": 1, "status": "ok", "legal_basis_ref": 1, "caveats_ref": 1,
"verdict": { "rule_id": "INV-VAT-D02", "mechanism": "standard", "liable": "supplier",
"place_of_supply": { "country": "FR" },
"rate": { "country": "FR", "kind": "standard", "percentage": "20" },
"rationale": "A VAT identification number of the customer, issued by another Member State and valid at the time of the supply, is a substantive condition of the exemption (Art. 138(1)(b)). It is declared as invalid or unknown here, so the exemption is not granted and the supply is taxed in the Member State of dispatch.",
"computed": { "taxable_amount": "4000.00", "rate_percentage": "20", "vat_amount": "800.00", "total_amount": "4800.00" } } },
{ "index": 2, "status": "ok", "legal_basis_ref": 2, "caveats_ref": 2,
"verdict": { "rule_id": "INV-VAT-X01", "mechanism": "exempt_with_credit", "liable": "supplier" } }
],
"summary": { "items": 3, "ok": 3, "errors": 0,
"mechanisms": { "standard": 1, "reverse_charge": 1, "exempt_with_credit": 1, "out_of_scope": 0, "undetermined": 0 } }
},
"provenance": { "source": "directive-2006-112 + tedb", "freshness": { "kind": "snapshot", "as_of": "2026-07-27T00:00:00Z" } }
}
Two rates are in play on line 0 and they say different things: rate.percentage
is what the Italian customer self-assesses, computed.rate_percentage is what
the French supplier charges — zero. An agent filling the invoice reads
computed; an agent booking the acquisition reads rate.
summary is the ledger-level read
summary.mechanisms carries all five keys, zeros included — an absent key
would read as an omission rather than a count of zero. It is what an agent
scans first: how many lines carry a compulsory mention, how many left the
Union’s territorial scope, and how many came back undetermined and therefore
need a human or a different source.
The x402 golden rule, applied to a ledger
The agent pays for the answer to its question, and “197 lines judged, 2 need your VAT numbers verified, 1 row is malformed” is a successful answer. A well-formed batch returns 200 whatever the mix inside it:
- A malformed item does not sink the call. It comes back at its position
with
status: "error"and the exactcode/message/issues[]a single call would have produced — an unknownsupply_kind, for instance, is refused by name with the admitted values listed back. - An
undeterminedline isok, not an error. It is a judged line whose judgement is a motivated refusal, counted as such insummary. An uncovered regime answered with a plausible default would be worse than no answer. - Only the envelope can fail: an unreadable body, an absent or empty
items, or more than 200 transactions — a400 invalid_batch_sizechecked before any judging, and not billed.
The batch settles as one x402 payment and is priced per transaction by the
gateway, so the cost tracks the size of the ledger rather than the number of
round-trips. The authoritative price and the accepted rails are served live by
the catalog — never hardcoded here. The
payment loop is the usual discover → 402 → pay → replay cycle; the
Quickstart walks it with runnable snippets and
For agents covers the discovery surfaces.
A concrete shape: the month-close loop
- Project the sales journal onto the item shape: two countries, goods or
services, business or consumer, the declared
supply_kind, the chargeability date, and the amount when you want VAT computed. - Judge the ledger: one
POST /invoice/vat-treatment/batch, one402, one signature, one replay, up to 200 lines. - Split on
summary. Lines withmechanism: "undetermined"and lines withstatus: "error"leave the automated path; the rest carry their mentions. - Verify what was taken as declared. Every
customer_vat_checkblock whose status isnot_checkedpoints atGET /company/vat, the live VIES lookup — a valid customer VAT number is a substantive condition of the intra-EU exemption (Art. 138(1)(b)), so line 1 above flips fromstandardtoreverse_chargeonly once the number checks out. Re-judge the corrected lines. - Emit. Pass each determination to
POST /invoice/generatethrough itsvat_treatmentfield and the compulsory mention is written into the invoice for you. - Log
provenance.freshness.as_ofnext to the closed period — the audit answer to “judged against which rates?”.
Steps 4 and 5 are why the batch is a pre-emission gate rather than a report: the correction happens before the invoice exists, not after a tax authority reads it.
What this batch will not do
The scope is exactly the single call’s scope, and the six sentences that state
it travel in data.limits with every answer:
- OSS and IOSS, and the distance-selling thresholds of Art. 33 and 59c, are
not applied — a line falling under them comes back
undetermined. - Import VAT and customs procedures (Art. 60, 201) are not determined: the person liable is designated by the Member State of importation.
- Margin schemes, investment gold, chain and triangular transactions beyond the two declared parties: not judged, no transport ascribed.
- No product classification. The rate served is the standard rate of
the place of supply; reduced rates come back as candidates only, and
has_regional_ratesflags States with territory-specific rates outside the snapshot. - The verdict rests on what the request declares. The runtime performs no
registry or VIES lookup — that is
/company/vatand/company/resolve.
It is a determination, not tax advice, and it files nothing: no VAT return, no
recapitulative statement, no Intrastat. The articles it cites are readable at a
date through GET /legal/eu-act, and the rates come
from a committed extract of the Commission’s Taxes in Europe Database covering
2016 onwards — a date before it is refused, never extrapolated.
Used for what it is — one settled call that renders a sourced verdict on every
line of a ledger against one dated snapshot — the batch makes VAT determination
cheap enough to run before the invoices are cut. For the request contract,
the field tables and the error codes, see the
POST /invoice/vat-treatment/batch documentation.