Alpha testing: all current functionality is free while VAT Engine is in active development

VAT Engine journal

What Should a VAT Calculation Record Actually Contain?

A VAT result is more useful when you can explain it later. Here is what a calculation record should preserve beyond the final tax amount.

By Vasyl KyryliukPublished Updated 11 min read
  • Engineering
VAT Engine blog cover illustrating the fields that make a VAT calculation record explainable, including amounts, rate, date, source, and provenance.

A VAT calculation can look deceptively simple.

You send an amount and a country. The system returns a VAT amount.

For example:

Net:   €100.00
VAT:    €19.00
Gross: €119.00

If all you need is the number right now, that may appear sufficient.

The problem starts six months later.

Someone asks:

  • Why was 19% used?
  • Which product tax class was selected?
  • Which transaction date determined the rate?
  • Was the input gross or net?
  • Which store or checkout produced the calculation?
  • Which rate dataset was used?
  • Has the calculation logic changed since then?
  • Can we reproduce the exact result?

If the only thing you stored was 19.00, most of those questions are impossible to answer reliably.

A useful VAT calculation record therefore needs to preserve more than the result.

It needs to preserve enough context, identity, and provenance to explain how that result came into existence.

The result is only one part of the record

A calculation record has several different responsibilities.

At minimum, it should let you answer four questions:

  1. What was calculated?
  2. Which inputs and tax context were used?
  3. Which rate and calculation logic produced the result?
  4. Can the result be reproduced later?

That means the record needs more than:

{
  "vat": 1900
}

A more useful model starts looking like this:

{
  "calculation_id": "e2d0940e-b123-47b7-80ad-8a3673fa4680",
  "country": "DE",
  "currency": "EUR",
  "vat_rate_bps": 1900,
  "gross_amount_minor": 11900,
  "net_amount_minor": 10000,
  "vat_amount_minor": 1900,
  "transaction_date": "2026-01-28T00:00:00Z"
}

Even this is only the beginning.

1. Start with a stable calculation identity

Every persisted calculation should have its own stable identifier.

For example:

calculation_id

This gives other systems something durable to reference.

A checkout, support ticket, export, reconciliation process, or internal business record can retain that ID without copying every implementation detail.

It also makes later retrieval straightforward:

GET /v1/transactions/{id}

In VAT Engine, calculation_id is the same identity exposed by the Transactions API rather than a second unrelated identifier.

That sounds like a small design choice, but duplicate identifiers quickly make financial systems harder to reason about.

One calculation should have one canonical identity.

2. Preserve the monetary values explicitly

A VAT calculation record should not store only the VAT amount.

You usually want all three values:

net
VAT
gross

For example:

{
  "net_amount_minor": 10000,
  "vat_amount_minor": 1900,
  "gross_amount_minor": 11900
}

This matters because one value should not need to be reverse-engineered later from another.

It also makes reconciliation easier.

If an external system says:

Gross: €119.00
VAT:   €19.00

you can compare the recorded values directly rather than reconstructing them using whatever calculation rules happen to exist today.

VAT Engine represents monetary values using integer minor units.

So:

€119.00 → 11900
€19.00  → 1900

This avoids making binary floating point the canonical representation of money.

I covered the reasoning in more detail here:

Why Money Calculations Should Not Use Floating Point (opens in a new tab)

3. Record the rate — but not just the rate

You should obviously know which VAT rate was applied.

For example:

{
  "vat_rate_bps": 1900
}

VAT Engine represents VAT rates in basis points:

1900 = 19.00%
700  = 7.00%

But storing 1900 alone is not enough.

A rate without context raises another question:

Why was this the applicable 19% rate?

That question requires provenance.

A calculation record should ideally distinguish between:

the numeric rate

and:

the evidence explaining where that rate came from

Those are not the same thing.

4. Transaction date is part of the tax context

Tax rates and treatment rules can change over time.

So the record should preserve the transaction or supply date used by the calculation.

For example:

{
  "transaction_date": "2026-01-28T00:00:00Z"
}

This is particularly important for:

  • refunds
  • corrections
  • historical imports
  • migrations
  • support investigations
  • audits
  • delayed reconciliation

Recalculating an old transaction with today's rate can produce a perfectly valid calculation for the wrong date.

That is why a VAT API should treat the date as part of the calculation context rather than incidental metadata.

5. Preserve the tax classification

Country alone does not determine VAT treatment.

The product or service classification matters too.

A record should therefore preserve whichever classification was actually used.

In a simple tax-class model that might be:

{
  "tax_class_id": "standard"
}

Other systems may use:

  • CN codes
  • CPA codes
  • HS codes
  • merchant-defined tax categories
  • governed mappings between classifications

The important rule is that the record should preserve the actual classification input or resolved mapping, not merely the resulting percentage.

Otherwise two calculations using the same numeric rate can become indistinguishable even though they reached that rate through different assumptions.

VAT Engine exposes its public tax-class catalogue here:

Tax Classes API (opens in a new tab)

6. Preserve whether the input included VAT

Consider these two requests:

€100 net + 19% VAT

and:

€100 gross including 19% VAT

They do not mean the same thing.

The first produces:

Net:   €100.00
VAT:    €19.00
Gross: €119.00

The second requires VAT extraction:

Net:   €84.03
VAT:   €15.97
Gross: €100.00

So the amount basis is part of the calculation contract.

VAT Engine makes this explicit through:

{
  "price_includes_vat": true
}

or:

{
  "price_includes_vat": false
}

A stored record should retain enough information to know which interpretation was used.

For more on the arithmetic:

VAT-Inclusive vs VAT-Exclusive Pricing: The Math Developers Get Wrong (opens in a new tab)

7. Rate provenance should be a first-class field

A useful VAT record should not merely say:

rate = 19%

It should be able to explain the status of the evidence behind that rate.

VAT Engine exposes that separately as:

{
  "rate_provenance": {
    "schema_version": "rate-evidence/v1",
    "evidence_status": "legacy_unverifiable",
    "source": "legacy_recorded_window",
    "effective_at": "2026-01-28T00:00:00Z",
    "source_evidence": []
  }
}

The important part here is not the exact schema.

It is the distinction between:

numeric result

and:

strength of evidence behind that result

For example, historical data should not suddenly become verified merely because its numeric value happens to match a newer reviewed rate.

VAT Engine deliberately keeps older records marked as:

legacy_unverifiable

when the required source-supported historical evidence was not captured.

That is more useful than pretending certainty exists where it does not.

8. Record which calculation logic produced the result

Rates are only half of the equation.

The arithmetic implementation itself can also change.

Imagine that a system changes:

  • rounding behavior
  • currency-scale handling
  • inclusive VAT extraction
  • validation rules
  • allocation behavior

You now need to know which version created an old result.

That is why calculation provenance matters.

A record can retain fields such as:

{
  "calculation_provenance": {
    "schema_version": "calculation-evidence/v1",
    "calculation_version": "vat-calculator-half-up/v1",
    "execution_digest": "...",
    "algorithm_manifest_version": "vat-algorithm-manifest/v1",
    "algorithm_manifest_digest": "...",
    "evidence_status": "captured",
    "replay_status": "ineligible"
  }
}

This gives the result a technical identity.

Instead of saying:

"Our calculator currently returns this."

you can say:

"This calculation was produced using this recorded calculation contract."

That distinction becomes increasingly important as financial software evolves.

9. Source attribution helps explain where the calculation came from

A merchant may have several systems producing VAT calculations:

Shopify
WooCommerce
subscription billing
mobile checkout
POS
custom headless store

A stable source identifier helps distinguish them.

VAT Engine supports an optional:

X-Source-ID

For example:

headless-checkout
shopify-orders
stripe-subscriptions

That value can later appear as source_id in transaction history and reporting.

Importantly, the source tag does not change the VAT rate or amounts.

It is reporting and attribution metadata.

That separation is useful:

tax inputs determine the calculation
source metadata tells you where the calculation originated

Source identifiers should also remain non-sensitive.

Do not put things like:

  • customer names
  • email addresses
  • access tokens
  • API credentials
  • individual order numbers

into a general source tag.

10. Evidence status should not be reduced to a boolean

A tempting model is:

{
  "verified": true
}

That usually loses too much information.

Evidence can exist in several meaningful states.

For example:

verified
legacy_unverifiable
unavailable

Calculation evidence may similarly distinguish between captured evidence and records that cannot be proven to the same standard.

This makes uncertainty explicit.

Financial systems become difficult to audit when:

unknown

is silently converted into:

false

or:

verified

A typed status is usually much safer.

11. Replay eligibility is different from evidence capture

Having evidence does not automatically mean you can reproduce a calculation exactly.

Exact replay requires enough preserved information to reconstruct the original decision without silently substituting today's data.

VAT Engine therefore treats replay as a separate state:

not_supported
ineligible
available

Only records reporting:

replay_status: available

are eligible for exact replay.

The replay endpoint can then use the original canonical request, pinned historical rate evidence, and registered calculator version:

POST /v1/transactions/{id}/replay

A successful exact replay returns the reproduced result rather than asking the caller to choose a different rate version.

That distinction is important.

If the caller selects a newer historical dataset or a different calculator version, that is no longer replay.

It is a new, counterfactual calculation.

12. Idempotency belongs near the calculation boundary

A calculation record is also easier to trust when network retries cannot accidentally create different stored results.

For operations that can be retried, an idempotency key can bind the original request.

VAT Engine supports:

Idempotency-Key

for authenticated calculations.

The same key with the same canonical request returns the existing calculation.

The same key with a different request fails instead of silently mutating the record.

That matters because distributed systems retry.

A timeout does not necessarily mean:

the calculation failed

It may mean:

the calculation succeeded but the response was lost

Stable calculation identity plus idempotency makes that situation much easier to handle safely.

13. A calculation record is not the same as a sales ledger

This distinction is especially important.

A VAT calculation can be useful evidence without being proof that a sale actually happened.

For example, a merchant could call a calculator while:

  • previewing a checkout
  • testing an integration
  • quoting a price
  • simulating a transaction
  • debugging an order

That does not mean the transaction should automatically enter OSS reporting.

VAT Engine therefore separates:

VAT calculation history

from:

committed sales data

Calling:

POST /v1/vat/calculate

records calculation history, but does not by itself add a sale to:

  • OSS threshold monitoring
  • Filing Prep
  • OSS/IOSS reporting

Those workflows rely on committed supply data.

This prevents an API calculation from being mistaken for an accounting event.

The Transactions API documentation makes this distinction explicit:

VAT Engine Transactions API (opens in a new tab)

14. A record should preserve facts, not force conclusions

There is another architectural benefit to richer records.

You can improve interpretation later without rewriting history.

For example, if you preserve:

transaction date
classification
amount basis
source
rate evidence
calculation version
result

then future tooling can review that record with more context.

If you preserve only:

VAT = €19

most of that opportunity disappears.

This is why provenance-heavy systems can look verbose.

The extra fields are not there because JSON needs to be complicated.

They are there because financial decisions often need to survive longer than the code that originally produced them.

What I would consider the minimum useful record

For a straightforward VAT calculation API, I would want at least:

Stable calculation ID

Inputs:
- country / jurisdiction
- currency
- input amount
- whether VAT was included
- tax classification
- transaction date

Outputs:
- net amount
- VAT amount
- gross amount
- applied VAT rate

Context:
- source/store/channel identifier where useful

Evidence:
- rate evidence status
- rate source/version
- calculation version
- calculation evidence status
- replay status

For more complex tax systems, the model may need to go further.

For example, a transaction can contain separate tax or obligation components rather than one flat rate.

In that case the record should preserve those components individually rather than flattening them into a number that loses the calculation structure.

What not to store as your only record

These patterns tend to cause trouble later.

Only the final VAT amount

{
  "vat": 1900
}

You cannot explain the rate, date, classification, or arithmetic.

Only the rate

{
  "rate": 0.19
}

You do not know which amount it applied to or why that rate was selected.

A mutable reference to "current rate"

If historical records resolve through whatever the current configuration happens to be, old calculations can effectively change meaning.

Money as floating point

{
  "gross": 119.00
}

JSON may look harmless, but using binary floating point as the application's canonical monetary representation introduces unnecessary ambiguity.

A generic verified: true

It hides whether rate evidence, calculation evidence, and replay evidence were actually available.

The useful question is not "what did we calculate?"

The useful question is:

Can we explain why this exact result exists?

That changes the shape of the system.

A calculator returns numbers.

A useful calculation record preserves:

identity
inputs
context
result
rate evidence
calculation evidence
reproducibility

That makes debugging easier.

It makes reconciliation easier.

It makes historical review easier.

And it prevents future versions of your own software from silently rewriting the meaning of old calculations.

For financial APIs, that is usually worth a few extra fields.


You can see the current VAT Engine calculation contract here:

Calculate VAT API (opens in a new tab)

And the calculation-history/evidence model here:

Transactions API (opens in a new tab)

VAT Engine is currently in active alpha development. This article describes engineering and API design considerations and is not tax or legal advice.


About Vasyl Kyryliuk

Solo founder and software engineer building VAT Engine, focused on EU VAT infrastructure, ecommerce integrations, APIs, security, and SaaS.

Report a correction