<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: VAT Engine</title>
    <description>The latest articles on DEV Community by VAT Engine (vat-engine).</description>
    <link>https://dev.to/vat-engine</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14227%2F9387a66e-23a7-4e90-a238-4a515aebc13d.jpg</url>
      <title>DEV Community: VAT Engine</title>
      <link>https://dev.to/vat-engine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vat-engine"/>
    <language>en</language>
    <item>
      <title>What Should a VAT Calculation Record Actually Contain?</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Thu, 08 Oct 2026 19:01:28 +0000</pubDate>
      <link>https://dev.to/vat-engine/what-should-a-vat-calculation-record-actually-contain-5f18</link>
      <guid>https://dev.to/vat-engine/what-should-a-vat-calculation-record-actually-contain-5f18</guid>
      <description>&lt;p&gt;A VAT calculation can look deceptively simple.&lt;/p&gt;

&lt;p&gt;You send an amount and a country. The system returns a VAT amount.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €100.00
VAT:    €19.00
Gross: €119.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all you need is the number right now, that may appear sufficient.&lt;/p&gt;

&lt;p&gt;The problem starts six months later.&lt;/p&gt;

&lt;p&gt;Someone asks:&lt;/p&gt;

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

&lt;p&gt;If the only thing you stored was &lt;code&gt;19.00&lt;/code&gt;, most of those questions are impossible to answer reliably.&lt;/p&gt;

&lt;p&gt;A useful VAT calculation record therefore needs to preserve more than the result.&lt;/p&gt;

&lt;p&gt;It needs to preserve enough &lt;strong&gt;context, identity, and provenance&lt;/strong&gt; to explain how that result came into existence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result is only one part of the record
&lt;/h2&gt;

&lt;p&gt;A calculation record has several different responsibilities.&lt;/p&gt;

&lt;p&gt;At minimum, it should let you answer four questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;What was calculated?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which inputs and tax context were used?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which rate and calculation logic produced the result?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can the result be reproduced later?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That means the record needs more than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A more useful model starts looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"calculation_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e2d0940e-b123-47b7-80ad-8a3673fa4680"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_rate_bps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"net_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-28T00:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even this is only the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with a stable calculation identity
&lt;/h2&gt;

&lt;p&gt;Every persisted calculation should have its own stable identifier.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calculation_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives other systems something durable to reference.&lt;/p&gt;

&lt;p&gt;A checkout, support ticket, export, reconciliation process, or internal business record can retain that ID without copying every implementation detail.&lt;/p&gt;

&lt;p&gt;It also makes later retrieval straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /v1/transactions/{id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In VAT Engine, &lt;code&gt;calculation_id&lt;/code&gt; is the same identity exposed by the Transactions API rather than a second unrelated identifier.&lt;/p&gt;

&lt;p&gt;That sounds like a small design choice, but duplicate identifiers quickly make financial systems harder to reason about.&lt;/p&gt;

&lt;p&gt;One calculation should have one canonical identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Preserve the monetary values explicitly
&lt;/h2&gt;

&lt;p&gt;A VAT calculation record should not store only the VAT amount.&lt;/p&gt;

&lt;p&gt;You usually want all three values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net
VAT
gross
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"net_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because one value should not need to be reverse-engineered later from another.&lt;/p&gt;

&lt;p&gt;It also makes reconciliation easier.&lt;/p&gt;

&lt;p&gt;If an external system says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross: €119.00
VAT:   €19.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can compare the recorded values directly rather than reconstructing them using whatever calculation rules happen to exist today.&lt;/p&gt;

&lt;p&gt;VAT Engine represents monetary values using integer minor units.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€119.00 → 11900
€19.00  → 1900
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids making binary floating point the canonical representation of money.&lt;/p&gt;

&lt;p&gt;I covered the reasoning in more detail here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vat-engine.app/blog/why-money-calculations-should-not-use-floating-point" rel="noopener noreferrer"&gt;Why Money Calculations Should Not Use Floating Point&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Record the rate — but not just the rate
&lt;/h2&gt;

&lt;p&gt;You should obviously know which VAT rate was applied.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_rate_bps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT Engine represents VAT rates in basis points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1900 = 19.00%
700  = 7.00%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But storing &lt;code&gt;1900&lt;/code&gt; alone is not enough.&lt;/p&gt;

&lt;p&gt;A rate without context raises another question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why was this the applicable 19% rate?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question requires provenance.&lt;/p&gt;

&lt;p&gt;A calculation record should ideally distinguish between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the numeric rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the evidence explaining where that rate came from
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are not the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Transaction date is part of the tax context
&lt;/h2&gt;

&lt;p&gt;Tax rates and treatment rules can change over time.&lt;/p&gt;

&lt;p&gt;So the record should preserve the transaction or supply date used by the calculation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-28T00:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is particularly important for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refunds&lt;/li&gt;
&lt;li&gt;corrections&lt;/li&gt;
&lt;li&gt;historical imports&lt;/li&gt;
&lt;li&gt;migrations&lt;/li&gt;
&lt;li&gt;support investigations&lt;/li&gt;
&lt;li&gt;audits&lt;/li&gt;
&lt;li&gt;delayed reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recalculating an old transaction with today's rate can produce a perfectly valid calculation for the wrong date.&lt;/p&gt;

&lt;p&gt;That is why a VAT API should treat the date as part of the calculation context rather than incidental metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Preserve the tax classification
&lt;/h2&gt;

&lt;p&gt;Country alone does not determine VAT treatment.&lt;/p&gt;

&lt;p&gt;The product or service classification matters too.&lt;/p&gt;

&lt;p&gt;A record should therefore preserve whichever classification was actually used.&lt;/p&gt;

&lt;p&gt;In a simple tax-class model that might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax_class_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standard"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other systems may use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CN codes&lt;/li&gt;
&lt;li&gt;CPA codes&lt;/li&gt;
&lt;li&gt;HS codes&lt;/li&gt;
&lt;li&gt;merchant-defined tax categories&lt;/li&gt;
&lt;li&gt;governed mappings between classifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important rule is that the record should preserve the &lt;strong&gt;actual classification input or resolved mapping&lt;/strong&gt;, not merely the resulting percentage.&lt;/p&gt;

&lt;p&gt;Otherwise two calculations using the same numeric rate can become indistinguishable even though they reached that rate through different assumptions.&lt;/p&gt;

&lt;p&gt;VAT Engine exposes its public tax-class catalogue here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;Tax Classes API&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Preserve whether the input included VAT
&lt;/h2&gt;

&lt;p&gt;Consider these two requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€100 net + 19% VAT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€100 gross including 19% VAT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They do not mean the same thing.&lt;/p&gt;

&lt;p&gt;The first produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €100.00
VAT:    €19.00
Gross: €119.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second requires VAT extraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €84.03
VAT:   €15.97
Gross: €100.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the amount basis is part of the calculation contract.&lt;/p&gt;

&lt;p&gt;VAT Engine makes this explicit through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stored record should retain enough information to know which interpretation was used.&lt;/p&gt;

&lt;p&gt;For more on the arithmetic:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vat-engine.app/blog/vat-inclusive-vs-vat-exclusive-pricing" rel="noopener noreferrer"&gt;VAT-Inclusive vs VAT-Exclusive Pricing: The Math Developers Get Wrong&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Rate provenance should be a first-class field
&lt;/h2&gt;

&lt;p&gt;A useful VAT record should not merely say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rate = 19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should be able to explain the status of the evidence behind that rate.&lt;/p&gt;

&lt;p&gt;VAT Engine exposes that separately as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rate_provenance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rate-evidence/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"evidence_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"legacy_unverifiable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"legacy_recorded_window"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"effective_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-28T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part here is not the exact schema.&lt;/p&gt;

&lt;p&gt;It is the distinction between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;numeric result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strength of evidence behind that result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, historical data should not suddenly become &lt;code&gt;verified&lt;/code&gt; merely because its numeric value happens to match a newer reviewed rate.&lt;/p&gt;

&lt;p&gt;VAT Engine deliberately keeps older records marked as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;legacy_unverifiable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when the required source-supported historical evidence was not captured.&lt;/p&gt;

&lt;p&gt;That is more useful than pretending certainty exists where it does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Record which calculation logic produced the result
&lt;/h2&gt;

&lt;p&gt;Rates are only half of the equation.&lt;/p&gt;

&lt;p&gt;The arithmetic implementation itself can also change.&lt;/p&gt;

&lt;p&gt;Imagine that a system changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rounding behavior&lt;/li&gt;
&lt;li&gt;currency-scale handling&lt;/li&gt;
&lt;li&gt;inclusive VAT extraction&lt;/li&gt;
&lt;li&gt;validation rules&lt;/li&gt;
&lt;li&gt;allocation behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You now need to know which version created an old result.&lt;/p&gt;

&lt;p&gt;That is why calculation provenance matters.&lt;/p&gt;

&lt;p&gt;A record can retain fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"calculation_provenance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"calculation-evidence/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"calculation_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vat-calculator-half-up/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"execution_digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"algorithm_manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vat-algorithm-manifest/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"algorithm_manifest_digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"evidence_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"captured"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replay_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ineligible"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the result a technical identity.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Our calculator currently returns this."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you can say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This calculation was produced using this recorded calculation contract."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction becomes increasingly important as financial software evolves.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Source attribution helps explain where the calculation came from
&lt;/h2&gt;

&lt;p&gt;A merchant may have several systems producing VAT calculations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shopify
WooCommerce
subscription billing
mobile checkout
POS
custom headless store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stable source identifier helps distinguish them.&lt;/p&gt;

&lt;p&gt;VAT Engine supports an optional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-Source-ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;headless-checkout
shopify-orders
stripe-subscriptions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That value can later appear as &lt;code&gt;source_id&lt;/code&gt; in transaction history and reporting.&lt;/p&gt;

&lt;p&gt;Importantly, the source tag does &lt;strong&gt;not&lt;/strong&gt; change the VAT rate or amounts.&lt;/p&gt;

&lt;p&gt;It is reporting and attribution metadata.&lt;/p&gt;

&lt;p&gt;That separation is useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tax inputs determine the calculation
source metadata tells you where the calculation originated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source identifiers should also remain non-sensitive.&lt;/p&gt;

&lt;p&gt;Do not put things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer names&lt;/li&gt;
&lt;li&gt;email addresses&lt;/li&gt;
&lt;li&gt;access tokens&lt;/li&gt;
&lt;li&gt;API credentials&lt;/li&gt;
&lt;li&gt;individual order numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;into a general source tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Evidence status should not be reduced to a boolean
&lt;/h2&gt;

&lt;p&gt;A tempting model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That usually loses too much information.&lt;/p&gt;

&lt;p&gt;Evidence can exist in several meaningful states.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verified
legacy_unverifiable
unavailable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calculation evidence may similarly distinguish between captured evidence and records that cannot be proven to the same standard.&lt;/p&gt;

&lt;p&gt;This makes uncertainty explicit.&lt;/p&gt;

&lt;p&gt;Financial systems become difficult to audit when:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is silently converted into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typed status is usually much safer.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Replay eligibility is different from evidence capture
&lt;/h2&gt;

&lt;p&gt;Having evidence does not automatically mean you can reproduce a calculation exactly.&lt;/p&gt;

&lt;p&gt;Exact replay requires enough preserved information to reconstruct the original decision without silently substituting today's data.&lt;/p&gt;

&lt;p&gt;VAT Engine therefore treats replay as a separate state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;not_supported
ineligible
available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only records reporting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;replay_status: available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are eligible for exact replay.&lt;/p&gt;

&lt;p&gt;The replay endpoint can then use the original canonical request, pinned historical rate evidence, and registered calculator version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /v1/transactions/{id}/replay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful exact replay returns the reproduced result rather than asking the caller to choose a different rate version.&lt;/p&gt;

&lt;p&gt;That distinction is important.&lt;/p&gt;

&lt;p&gt;If the caller selects a newer historical dataset or a different calculator version, that is no longer replay.&lt;/p&gt;

&lt;p&gt;It is a new, counterfactual calculation.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Idempotency belongs near the calculation boundary
&lt;/h2&gt;

&lt;p&gt;A calculation record is also easier to trust when network retries cannot accidentally create different stored results.&lt;/p&gt;

&lt;p&gt;For operations that can be retried, an idempotency key can bind the original request.&lt;/p&gt;

&lt;p&gt;VAT Engine supports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Idempotency-Key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for authenticated calculations.&lt;/p&gt;

&lt;p&gt;The same key with the same canonical request returns the existing calculation.&lt;/p&gt;

&lt;p&gt;The same key with a different request fails instead of silently mutating the record.&lt;/p&gt;

&lt;p&gt;That matters because distributed systems retry.&lt;/p&gt;

&lt;p&gt;A timeout does not necessarily mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the calculation failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the calculation succeeded but the response was lost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stable calculation identity plus idempotency makes that situation much easier to handle safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. A calculation record is not the same as a sales ledger
&lt;/h2&gt;

&lt;p&gt;This distinction is especially important.&lt;/p&gt;

&lt;p&gt;A VAT calculation can be useful evidence without being proof that a sale actually happened.&lt;/p&gt;

&lt;p&gt;For example, a merchant could call a calculator while:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;previewing a checkout&lt;/li&gt;
&lt;li&gt;testing an integration&lt;/li&gt;
&lt;li&gt;quoting a price&lt;/li&gt;
&lt;li&gt;simulating a transaction&lt;/li&gt;
&lt;li&gt;debugging an order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That does not mean the transaction should automatically enter OSS reporting.&lt;/p&gt;

&lt;p&gt;VAT Engine therefore separates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT calculation history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;committed sales data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /v1/vat/calculate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;records calculation history, but does &lt;strong&gt;not&lt;/strong&gt; by itself add a sale to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OSS threshold monitoring&lt;/li&gt;
&lt;li&gt;Filing Prep&lt;/li&gt;
&lt;li&gt;OSS/IOSS reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those workflows rely on committed supply data.&lt;/p&gt;

&lt;p&gt;This prevents an API calculation from being mistaken for an accounting event.&lt;/p&gt;

&lt;p&gt;The Transactions API documentation makes this distinction explicit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;VAT Engine Transactions API&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  14. A record should preserve facts, not force conclusions
&lt;/h2&gt;

&lt;p&gt;There is another architectural benefit to richer records.&lt;/p&gt;

&lt;p&gt;You can improve interpretation later without rewriting history.&lt;/p&gt;

&lt;p&gt;For example, if you preserve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction date
classification
amount basis
source
rate evidence
calculation version
result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then future tooling can review that record with more context.&lt;/p&gt;

&lt;p&gt;If you preserve only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = €19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;most of that opportunity disappears.&lt;/p&gt;

&lt;p&gt;This is why provenance-heavy systems can look verbose.&lt;/p&gt;

&lt;p&gt;The extra fields are not there because JSON needs to be complicated.&lt;/p&gt;

&lt;p&gt;They are there because financial decisions often need to survive longer than the code that originally produced them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would consider the minimum useful record
&lt;/h2&gt;

&lt;p&gt;For a straightforward VAT calculation API, I would want at least:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more complex tax systems, the model may need to go further.&lt;/p&gt;

&lt;p&gt;For example, a transaction can contain separate tax or obligation components rather than one flat rate.&lt;/p&gt;

&lt;p&gt;In that case the record should preserve those components individually rather than flattening them into a number that loses the calculation structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What not to store as your only record
&lt;/h2&gt;

&lt;p&gt;These patterns tend to cause trouble later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only the final VAT amount
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You cannot explain the rate, date, classification, or arithmetic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only the rate
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.19&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You do not know which amount it applied to or why that rate was selected.&lt;/p&gt;

&lt;h3&gt;
  
  
  A mutable reference to "current rate"
&lt;/h3&gt;

&lt;p&gt;If historical records resolve through whatever the current configuration happens to be, old calculations can effectively change meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Money as floating point
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;119.00&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSON may look harmless, but using binary floating point as the application's canonical monetary representation introduces unnecessary ambiguity.&lt;/p&gt;

&lt;h3&gt;
  
  
  A generic &lt;code&gt;verified: true&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;It hides whether rate evidence, calculation evidence, and replay evidence were actually available.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful question is not "what did we calculate?"
&lt;/h2&gt;

&lt;p&gt;The useful question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can we explain why this exact result exists?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That changes the shape of the system.&lt;/p&gt;

&lt;p&gt;A calculator returns numbers.&lt;/p&gt;

&lt;p&gt;A useful calculation record preserves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;identity
inputs
context
result
rate evidence
calculation evidence
reproducibility
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes debugging easier.&lt;/p&gt;

&lt;p&gt;It makes reconciliation easier.&lt;/p&gt;

&lt;p&gt;It makes historical review easier.&lt;/p&gt;

&lt;p&gt;And it prevents future versions of your own software from silently rewriting the meaning of old calculations.&lt;/p&gt;

&lt;p&gt;For financial APIs, that is usually worth a few extra fields.&lt;/p&gt;




&lt;p&gt;You can see the current VAT Engine calculation contract here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;Calculate VAT API&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the calculation-history/evidence model here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;Transactions API&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VAT Engine is currently in active alpha development. This article describes engineering and API design considerations and is not tax or legal advice.&lt;/p&gt;

</description>
      <category>api</category>
      <category>vat</category>
      <category>software</category>
      <category>saas</category>
    </item>
    <item>
      <title>Why Money Calculations Should Not Use Floating Point</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Fri, 02 Oct 2026 20:36:33 +0000</pubDate>
      <link>https://dev.to/vat-engine/why-money-calculations-should-not-use-floating-point-3fha</link>
      <guid>https://dev.to/vat-engine/why-money-calculations-should-not-use-floating-point-3fha</guid>
      <description>&lt;p&gt;Financial software often starts with code that looks completely harmless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;19.99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vatRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.19&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;vatRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The formula is not the surprising part.&lt;/p&gt;

&lt;p&gt;The representation is.&lt;/p&gt;

&lt;p&gt;Most mainstream programming languages represent values such as &lt;code&gt;19.99&lt;/code&gt;, &lt;code&gt;0.1&lt;/code&gt;, and &lt;code&gt;0.19&lt;/code&gt; using &lt;strong&gt;binary floating point&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That representation is extremely useful for scientific computing, graphics, statistics, simulations, and many other problems.&lt;/p&gt;

&lt;p&gt;Money has a different set of requirements.&lt;/p&gt;

&lt;p&gt;A payment, invoice, VAT amount, or accounting total usually needs to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reproducible;&lt;/li&gt;
&lt;li&gt;represented at a known currency precision;&lt;/li&gt;
&lt;li&gt;rounded according to an explicit rule;&lt;/li&gt;
&lt;li&gt;consistent across services;&lt;/li&gt;
&lt;li&gt;safe to persist and export;&lt;/li&gt;
&lt;li&gt;understandable later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those requirements make binary floating point a poor default &lt;strong&gt;canonical representation for money&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For VAT software, even a small inconsistency can eventually appear in net amounts, VAT amounts, gross totals, reports, exports, or reconciliation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Many decimal fractions cannot be represented exactly using binary floating point.&lt;/li&gt;
&lt;li&gt;Money is usually better modeled as an amount plus currency and scale.&lt;/li&gt;
&lt;li&gt;Integer minor units make the monetary input exact.&lt;/li&gt;
&lt;li&gt;VAT rates can also be represented using integer basis points.&lt;/li&gt;
&lt;li&gt;Rounding belongs in the calculation contract, not only in UI formatting.&lt;/li&gt;
&lt;li&gt;Decimal libraries are also valid when their precision and rounding rules are explicit.&lt;/li&gt;
&lt;li&gt;Correct arithmetic still requires the correct &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax class&lt;/a&gt;, rate, country, and transaction context.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The classic floating-point example
&lt;/h2&gt;

&lt;p&gt;Open a JavaScript console and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You do not get exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, JavaScript produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.30000000000000004
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a JavaScript bug.&lt;/p&gt;

&lt;p&gt;It is a consequence of binary floating-point representation.&lt;/p&gt;

&lt;p&gt;Many decimal fractions that are simple in base 10 do not have a finite representation in base 2.&lt;/p&gt;

&lt;p&gt;A similar problem exists in decimal notation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 / 3 = 0.333333...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no finite decimal representation of one third.&lt;/p&gt;

&lt;p&gt;Likewise, values such as &lt;code&gt;0.1&lt;/code&gt; cannot be represented exactly using a finite binary fraction.&lt;/p&gt;

&lt;p&gt;The runtime therefore stores the closest available approximation.&lt;/p&gt;

&lt;p&gt;For many applications, that approximation is perfectly acceptable.&lt;/p&gt;

&lt;p&gt;For money, it is usually better not to make that approximation part of the monetary domain model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Money is discrete at a defined currency precision
&lt;/h2&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€19.99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For ordinary EUR calculations, that amount can be represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,999 cents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;19.99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the core financial representation can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;priceMinor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// €0.10&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// €0.20&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no floating-point approximation involved in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 + 20 = 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can convert the value into a formatted monetary string later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 minor units → €0.30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT Engine follows this model in its &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;VAT calculation API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For EUR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€1.00   → 100
€19.99  → 1999
€119.00 → 11900
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API therefore uses fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gross_amount_minor
net_amount_minor
vat_amount_minor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than using binary floating-point monetary values as the core VAT calculation input and output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not every currency has two decimal places
&lt;/h2&gt;

&lt;p&gt;A common simplification is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Store everything in cents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That works for currencies such as EUR and USD, but it is not a general money model.&lt;/p&gt;

&lt;p&gt;Currencies can use different minor-unit scales.&lt;/p&gt;

&lt;p&gt;Conceptually, a monetary value therefore needs at least:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amount
currency
scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A domain type could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Money&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;amountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EUR → 2 decimal places
JPY → 0 decimal places
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other currencies can use different defined scales.&lt;/p&gt;

&lt;p&gt;VAT Engine's money model validates the currency and expected minor-unit scale instead of treating every currency as if it automatically had two decimal places.&lt;/p&gt;

&lt;h2&gt;
  
  
  Floating point mixes representation with rounding
&lt;/h2&gt;

&lt;p&gt;Suppose we calculate 19% VAT on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€19.99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A floating-point implementation may start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;19.99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vatRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.19&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;vatRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mathematically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19.99 × 0.19 = 3.7981
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EUR cannot represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€3.7981
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as a final monetary amount.&lt;/p&gt;

&lt;p&gt;At some point the result needs to be rounded.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€3.7981 → €3.80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That rounding decision is unavoidable.&lt;/p&gt;

&lt;p&gt;The problem with using floating point as the money representation is that approximation already exists &lt;strong&gt;before&lt;/strong&gt; the application's financial rounding policy is applied.&lt;/p&gt;

&lt;p&gt;When values move through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;browser
→ API
→ worker
→ database
→ report
→ export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;different implementations can also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;round at different stages;&lt;/li&gt;
&lt;li&gt;serialize numbers differently;&lt;/li&gt;
&lt;li&gt;aggregate before or after rounding;&lt;/li&gt;
&lt;li&gt;use different numeric types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A more predictable design keeps the monetary input exact and makes the rounding step explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minor units make the monetary input exact
&lt;/h2&gt;

&lt;p&gt;Represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€19.99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and represent the VAT rate separately.&lt;/p&gt;

&lt;p&gt;For a VAT-exclusive price with a 19% rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT =
1999 × 19 / 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;379.81 minor units
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The currency cannot represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.81
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;of a cent.&lt;/p&gt;

&lt;p&gt;Now we have reached the actual financial decision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How should the fractional minor unit be rounded?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using half-up rounding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;379.81 → 380
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €19.99
VAT:   €3.80
Gross: €23.79
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important difference is that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;was exact.&lt;/p&gt;

&lt;p&gt;Rounding happened deliberately at the monetary calculation boundary rather than being mixed with the representation of the input value.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAT rates can be represented as integers too
&lt;/h2&gt;

&lt;p&gt;Monetary amounts are not the only decimal-looking values involved in VAT.&lt;/p&gt;

&lt;p&gt;A VAT rate such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is often represented in application code as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.19&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the rate can also be represented using an integer.&lt;/p&gt;

&lt;p&gt;VAT Engine's calculation contract uses &lt;strong&gt;basis points&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19.00% = 1900 basis points
20.00% = 2000 basis points
7.00%  = 700 basis points
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A calculation response can therefore contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_rate_bps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a VAT-exclusive calculation, the core arithmetic can be expressed as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT minor units =
amount_minor × rate_bps / 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amount_minor = 1999
rate_bps     = 1900
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1999 × 1900 / 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fractional result is then rounded according to the defined calculation rule.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;Calculate VAT API&lt;/a&gt; exposes both money and VAT rates using these explicit integer representations.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAT-inclusive pricing still requires fractional arithmetic
&lt;/h2&gt;

&lt;p&gt;Integer representation does not mean that all intermediate mathematical results are integers.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross: €19.99
VAT rate: 19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gross_minor = 1999
rate_bps    = 1900
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because VAT is already included in the gross amount, it has to be extracted.&lt;/p&gt;

&lt;p&gt;The formula is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT =
Gross × Rate / (100% + Rate)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using basis points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT minor units =
1999 × 1900 / (10000 + 1900)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1999 × 1900 / 11900
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a fractional minor-unit result.&lt;/p&gt;

&lt;p&gt;That value is then rounded using the defined rounding rule.&lt;/p&gt;

&lt;p&gt;The important distinction is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Integer money representation does not eliminate rounding. It makes the point where rounding is required explicit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a deeper explanation of inclusive versus exclusive VAT arithmetic, see &lt;a href="https://vat-engine.app/blog/vat-inclusive-vs-vat-exclusive-pricing" rel="noopener noreferrer"&gt;VAT-Inclusive vs VAT-Exclusive Pricing: The Math Developers Get Wrong&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rounding is part of the financial contract
&lt;/h2&gt;

&lt;p&gt;Once an intermediate result contains a fraction of a minor unit, the application needs a defined policy.&lt;/p&gt;

&lt;p&gt;Possible approaches include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;round half up
round half away from zero
round half to even
truncate
round per line
round only after aggregation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These approaches are not interchangeable.&lt;/p&gt;

&lt;p&gt;For example, imagine several order lines where each line creates a fractional cent of VAT.&lt;/p&gt;

&lt;p&gt;One system might:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calculate → round every line → sum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while another might:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;calculate every line → sum exact intermediates → round once
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those systems can produce different totals.&lt;/p&gt;

&lt;p&gt;That does not necessarily mean either system has a floating-point bug.&lt;/p&gt;

&lt;p&gt;It means the &lt;strong&gt;rounding contract differs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;VAT Engine's core VAT calculation arithmetic works with integer minor units and applies deterministic half-up rounding to the VAT fraction.&lt;/p&gt;

&lt;p&gt;The behavior is documented in the &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;Calculate VAT API reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting is not calculation
&lt;/h2&gt;

&lt;p&gt;Another useful separation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;financial value
≠
display string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A core system might store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"net_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2379&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A European UI could display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€19.99
€3.80
€23.79
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another locale might display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19,99 €
3,80 €
23,79 €
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The formatting changed.&lt;/p&gt;

&lt;p&gt;The money did not.&lt;/p&gt;

&lt;p&gt;Currency formatting belongs at the presentation boundary.&lt;/p&gt;

&lt;p&gt;It should not define how the underlying amount is represented or calculated.&lt;/p&gt;

&lt;h2&gt;
  
  
  An integer alone is not money
&lt;/h2&gt;

&lt;p&gt;This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is not enough information.&lt;/p&gt;

&lt;p&gt;It could represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€19.99
$19.99
¥1,999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or another currency amount.&lt;/p&gt;

&lt;p&gt;Money therefore needs currency identity as part of the value.&lt;/p&gt;

&lt;p&gt;That also means this should not be accepted as ordinary addition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€10.00 + $10.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An explicit exchange-rate operation is required first.&lt;/p&gt;

&lt;p&gt;A money type can enforce rules that a primitive &lt;code&gt;number&lt;/code&gt; cannot.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Money&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;amountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding two values can require:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;same currency
+
same scale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before the arithmetic is allowed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exchange rates are a separate numeric domain
&lt;/h2&gt;

&lt;p&gt;There is an important nuance here.&lt;/p&gt;

&lt;p&gt;Saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not use binary floating point as the canonical representation of money.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;does &lt;strong&gt;not&lt;/strong&gt; mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every numeric value inside financial software must always be an integer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Exchange rates, ratios, measurements, or other values may require precision beyond a currency's final minor-unit scale.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 EUR = 4.3176 PLN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is a rate, not a monetary amount.&lt;/p&gt;

&lt;p&gt;A cleaner architecture treats these as different domain concepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Money    → currency + minor units
VAT rate → basis points
FX rate  → explicitly defined rate representation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The conversion into money then happens at a well-defined boundary with a known rounding policy.&lt;/p&gt;

&lt;p&gt;That is much easier to reason about than using one generic floating-point type for every financial concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decimal libraries are also a valid solution
&lt;/h2&gt;

&lt;p&gt;Integer minor units are not the only reasonable way to implement monetary arithmetic.&lt;/p&gt;

&lt;p&gt;Arbitrary-precision or fixed-point decimal libraries can also be appropriate.&lt;/p&gt;

&lt;p&gt;They are especially useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;intermediate values require more precision than the final currency scale;&lt;/li&gt;
&lt;li&gt;decimal quantities need to remain exact;&lt;/li&gt;
&lt;li&gt;financial formulas involve multiple precision stages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important requirements are still the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;precision should be explicit;&lt;/li&gt;
&lt;li&gt;scale should be explicit;&lt;/li&gt;
&lt;li&gt;rounding should be explicit;&lt;/li&gt;
&lt;li&gt;serialization should be predictable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the rule is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Only integers are acceptable for financial software.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not let binary floating-point behavior silently define the semantics of your money.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For APIs, integer minor units have the additional advantage of creating a simple cross-language contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every consumer sees the same integer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the amount basis explicit
&lt;/h2&gt;

&lt;p&gt;Correct monetary representation does not remove the need for correct business context.&lt;/p&gt;

&lt;p&gt;For VAT calculations, a system also needs to know whether the input represents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gross
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT Engine exposes this explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax_class_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-28"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A calculation can return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_rate_bps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"net_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full request and response contract is available in the &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;VAT calculation API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also experiment with gross and net calculations using the &lt;a href="https://vat-engine.app/vat-calculator" rel="noopener noreferrer"&gt;VAT calculator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exact money does not automatically mean correct VAT
&lt;/h2&gt;

&lt;p&gt;Using minor units solves a particular class of engineering problems.&lt;/p&gt;

&lt;p&gt;It does not determine the correct VAT treatment.&lt;/p&gt;

&lt;p&gt;A calculation can still depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;destination country;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax class&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;transaction date;&lt;/li&gt;
&lt;li&gt;selected VAT rate;&lt;/li&gt;
&lt;li&gt;relevant transaction facts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;integer arithmetic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not automatically imply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;correct VAT treatment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A more complete model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;correct transaction facts
+ correct rate selection
+ exact money representation
+ explicit rounding
= reproducible VAT calculation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT Engine separates these concerns.&lt;/p&gt;

&lt;p&gt;You can inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;Tax Classes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vat-engine.app/docs/api/rates" rel="noopener noreferrer"&gt;VAT Rates API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;Calculate VAT API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persist the canonical monetary representation
&lt;/h2&gt;

&lt;p&gt;A system can implement its calculator correctly and still lose that consistency later.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API calculation → integer minor units
database        → converted into floating point
frontend        → Number
export          → decimal regenerated from float
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original calculation was deterministic.&lt;/p&gt;

&lt;p&gt;The persistence contract was not.&lt;/p&gt;

&lt;p&gt;If minor units are the canonical representation, keep them canonical across:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request
→ calculation
→ persistence
→ read-back
→ export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT Engine's authenticated &lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;transaction calculation records&lt;/a&gt; retain the calculated minor-unit amounts together with their calculation context.&lt;/p&gt;

&lt;p&gt;That allows the stored result to be inspected later without reconstructing monetary amounts from presentation strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculation history is not a sales ledger
&lt;/h2&gt;

&lt;p&gt;There is another important boundary.&lt;/p&gt;

&lt;p&gt;A calculation does not necessarily represent a completed transaction.&lt;/p&gt;

&lt;p&gt;A checkout may calculate VAT repeatedly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer changes country
→ calculate

quantity changes
→ calculate

discount applied
→ calculate

shipping option changes
→ calculate

customer leaves
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those calculations can be useful for technical history and review.&lt;/p&gt;

&lt;p&gt;They are not necessarily completed sales.&lt;/p&gt;

&lt;p&gt;VAT Engine therefore keeps &lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;calculation history&lt;/a&gt; separate from committed supply events used by its compliance and reporting workflows.&lt;/p&gt;

&lt;p&gt;This prevents a calculation API call from being mistaken for a legal or accounting business event.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical TypeScript model
&lt;/h2&gt;

&lt;p&gt;Instead of making every monetary value a generic number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;use an explicit type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Money&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;amountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Money&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;amountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EUR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the VAT rate separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vatRateBps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A calculation result can then have a clear contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;VatResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;netAmountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;vatAmountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grossAmountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;vatRateBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;bigint&lt;/code&gt; here also avoids accidentally exceeding JavaScript's safe integer range in systems that may process very large amounts.&lt;/p&gt;

&lt;p&gt;For smaller bounded values, a regular integer &lt;code&gt;number&lt;/code&gt; can also be used safely when the range is explicitly constrained.&lt;/p&gt;

&lt;p&gt;The important part is that money semantics are deliberate rather than implicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Storing prices as floating-point numbers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;19.99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes binary approximation part of the canonical financial representation.&lt;/p&gt;

&lt;p&gt;Prefer a fixed-scale money model.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rounding only for display
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is presentation formatting.&lt;/p&gt;

&lt;p&gt;Financial rounding affects the actual stored amount and should happen at the defined calculation boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Assuming every currency uses two decimals
&lt;/h3&gt;

&lt;p&gt;Do not make:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amount / 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a universal currency rule.&lt;/p&gt;

&lt;p&gt;Currency scale belongs in the money model.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Treating an integer as sufficient monetary context
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without a currency and scale is not a complete money value.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Mixing currencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1000 EUR minor units
+
1000 USD minor units
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is not a valid ordinary addition.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Using decimals without defining rounding
&lt;/h3&gt;

&lt;p&gt;Decimal arithmetic can avoid binary approximation, but the application still needs to define what happens when a result exceeds the currency's supported precision.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Converting back to floating point during persistence
&lt;/h3&gt;

&lt;p&gt;A safe calculator cannot protect a system that later discards the canonical money representation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Money arithmetic should be boring
&lt;/h2&gt;

&lt;p&gt;The best financial arithmetic is rarely clever.&lt;/p&gt;

&lt;p&gt;It should be predictable.&lt;/p&gt;

&lt;p&gt;Before calculating a monetary result, the system should be able to answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What currency is this?
What is its minor-unit scale?
What amount is being calculated?
Does the amount include VAT?
Which VAT rate is being used?
Where does rounding happen?
Which rounding rule applies?
What values will be persisted?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When those decisions are explicit, a large class of subtle financial bugs becomes much easier to prevent.&lt;/p&gt;

&lt;p&gt;That is why VAT Engine's &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;VAT calculation API&lt;/a&gt; uses minor-unit monetary amounts, integer VAT basis points, an explicit price-inclusion flag, and deterministic VAT rounding for its core calculation path.&lt;/p&gt;

&lt;p&gt;The result is not sophisticated mathematics.&lt;/p&gt;

&lt;p&gt;That is exactly the point.&lt;/p&gt;

&lt;p&gt;Financial arithmetic should be simple enough to reproduce, test, store, and explain later.&lt;/p&gt;

&lt;p&gt;You can explore the &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;Calculate VAT API&lt;/a&gt;, browse available &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax classes&lt;/a&gt;, inspect &lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;calculation history&lt;/a&gt;, or try the &lt;a href="https://vat-engine.app/vat-calculator" rel="noopener noreferrer"&gt;free VAT calculator&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This article discusses monetary arithmetic and software design. It is not tax, accounting, or legal advice. The correct VAT treatment of a transaction depends on the applicable rules and the actual transaction facts.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>fintech</category>
      <category>api</category>
    </item>
    <item>
      <title>VAT-Inclusive vs VAT-Exclusive Pricing: The Math Developers Get Wrong</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Fri, 25 Sep 2026 20:18:49 +0000</pubDate>
      <link>https://dev.to/vat-engine/vat-inclusive-vs-vat-exclusive-pricing-the-math-developers-get-wrong-3eop</link>
      <guid>https://dev.to/vat-engine/vat-inclusive-vs-vat-exclusive-pricing-the-math-developers-get-wrong-3eop</guid>
      <description>&lt;p&gt;VAT calculation often looks like simple percentage arithmetic:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take the price, multiply it by the VAT rate, and you are done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That only works when you know exactly &lt;strong&gt;what the input price represents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A €100 net price with 19% VAT and a €100 VAT-inclusive price with 19% VAT are two different calculations.&lt;/p&gt;

&lt;p&gt;For developers building checkout, billing, ecommerce, or accounting systems, confusing those two cases is an easy way to create small errors that later propagate into transaction records, reports, reconciliation, and financial exports.&lt;/p&gt;

&lt;p&gt;The distinction is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VAT-exclusive price&lt;/strong&gt; → VAT must be &lt;strong&gt;added&lt;/strong&gt; to the net amount.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VAT-inclusive price&lt;/strong&gt; → VAT must be &lt;strong&gt;extracted&lt;/strong&gt; from the gross amount.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those operations do not use the same formula.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Net × VAT rate&lt;/code&gt; works when the input is VAT-exclusive.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Gross × VAT rate&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; the correct way to extract VAT from a VAT-inclusive price.&lt;/li&gt;
&lt;li&gt;VAT-inclusive extraction uses &lt;code&gt;Gross × Rate / (1 + Rate)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Money should be represented in integer minor units rather than binary floating point.&lt;/li&gt;
&lt;li&gt;Rounding needs to be part of the calculation contract.&lt;/li&gt;
&lt;li&gt;Correct arithmetic still depends on the correct country, &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax class&lt;/a&gt;, rate, and transaction date.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  VAT-exclusive pricing: add VAT to the net amount
&lt;/h2&gt;

&lt;p&gt;A VAT-exclusive amount represents the price &lt;strong&gt;before VAT&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net amount: €100.00
VAT rate:   19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT is calculated from the net amount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = Net × Rate
VAT = €100.00 × 0.19
VAT = €19.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross = Net + VAT
Gross = €100.00 + €19.00
Gross = €119.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Net&lt;/td&gt;
&lt;td&gt;€100.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VAT&lt;/td&gt;
&lt;td&gt;€19.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gross&lt;/td&gt;
&lt;td&gt;€119.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the straightforward case.&lt;/p&gt;

&lt;p&gt;VAT Engine's &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;Calculate VAT API&lt;/a&gt; makes the amount basis explicit with &lt;code&gt;price_includes_vat&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a VAT-exclusive amount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax_class_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-28"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means that the supplied amount is treated as a &lt;strong&gt;net input&lt;/strong&gt; and VAT is added on top.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;gross_amount_minor&lt;/code&gt; field name is retained by the VAT Engine API for compatibility. The actual amount basis is explicitly defined by &lt;code&gt;price_includes_vat&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a 19% VAT rate, the calculation produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_rate_bps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"net_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT Engine represents monetary amounts in &lt;strong&gt;minor currency units&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10000 = €100.00
1900  = €19.00
11900 = €119.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also experiment with the same calculation using the &lt;a href="https://vat-engine.app/vat-calculator" rel="noopener noreferrer"&gt;VAT calculator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAT-inclusive pricing: where the common mistake happens
&lt;/h2&gt;

&lt;p&gt;Now consider a customer-facing price of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€119.00 including 19% VAT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tempting calculation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€119.00 × 19% = €22.61
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that is wrong.&lt;/p&gt;

&lt;p&gt;The 19% rate applies to the &lt;strong&gt;net tax base&lt;/strong&gt;, not to a gross amount that already contains VAT.&lt;/p&gt;

&lt;p&gt;The €119 consists of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net + VAT = Gross
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT is already embedded inside the €119.&lt;/p&gt;

&lt;p&gt;So instead of adding 19%, we need to &lt;strong&gt;extract&lt;/strong&gt; the VAT portion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The correct formula for extracting VAT
&lt;/h2&gt;

&lt;p&gt;Let:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;G = Gross amount
r = VAT rate as a decimal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The net amount is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net = G / (1 + r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT is then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = G - Net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = G × r / (1 + r)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For €119 including 19% VAT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = 119 × 0.19 / 1.19
VAT = 19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net = 119 - 19
Net = 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Net&lt;/td&gt;
&lt;td&gt;€100.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VAT&lt;/td&gt;
&lt;td&gt;€19.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gross&lt;/td&gt;
&lt;td&gt;€119.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important part is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross × VAT rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is &lt;strong&gt;not&lt;/strong&gt; the VAT extraction formula.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the formulas are different
&lt;/h2&gt;

&lt;p&gt;With VAT-exclusive pricing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net   = 100%
VAT   = 19%
Gross = 119%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The VAT rate is expressed relative to the net amount.&lt;/p&gt;

&lt;p&gt;That makes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = Net × 19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;correct.&lt;/p&gt;

&lt;p&gt;With VAT-inclusive pricing, however, the supplied gross amount represents 119% of the net amount.&lt;/p&gt;

&lt;p&gt;The VAT portion of gross is therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19 / 119
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19 / 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is why adding VAT and extracting VAT cannot use the same multiplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the amount basis explicit
&lt;/h2&gt;

&lt;p&gt;Before performing VAT arithmetic, a system should know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does this amount already include VAT?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not try to infer that from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the currency;&lt;/li&gt;
&lt;li&gt;the destination country;&lt;/li&gt;
&lt;li&gt;the amount itself;&lt;/li&gt;
&lt;li&gt;the frontend where the value came from;&lt;/li&gt;
&lt;li&gt;whether the transaction appears to be B2C;&lt;/li&gt;
&lt;li&gt;how another system happens to display the price.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make the amount basis part of the API contract or data model.&lt;/p&gt;

&lt;p&gt;VAT Engine requires this explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it is &lt;code&gt;true&lt;/code&gt;, VAT is extracted from the supplied amount.&lt;/p&gt;

&lt;p&gt;When it is &lt;code&gt;false&lt;/code&gt;, VAT is added to the supplied amount.&lt;/p&gt;

&lt;p&gt;The complete contract is documented in the &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;Calculate VAT API reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same rate can produce different arithmetic
&lt;/h2&gt;

&lt;p&gt;Consider two requests using the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;country;&lt;/li&gt;
&lt;li&gt;currency;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax class&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;transaction date;&lt;/li&gt;
&lt;li&gt;VAT rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  VAT-exclusive input
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input: €100.00 net
Rate:  19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €100.00
VAT:   €19.00
Gross: €119.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VAT-inclusive input
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input: €100.00 gross
Rate:  19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the result is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €84.03
VAT:   €15.97
Gross: €100.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rate did not change.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;meaning of the input amount&lt;/strong&gt; changed.&lt;/p&gt;

&lt;p&gt;That is why an API that accepts only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without defining whether the amount is net or gross has an incomplete financial contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not use floating point for money
&lt;/h2&gt;

&lt;p&gt;Correct formulas are only part of the problem.&lt;/p&gt;

&lt;p&gt;Another common mistake is representing money using binary floating-point values.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;19.99&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.19&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;looks harmless.&lt;/p&gt;

&lt;p&gt;But many decimal fractions cannot be represented exactly in binary floating point. Small representation errors can then interact with rounding and aggregation.&lt;/p&gt;

&lt;p&gt;That becomes particularly uncomfortable when the same values move through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checkout
→ backend
→ database
→ reporting
→ export
→ accounting system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A safer approach is to represent monetary values using integer minor units.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€119.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11900
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for a currency with two decimal places.&lt;/p&gt;

&lt;p&gt;VAT Engine follows this model. The &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;calculation API&lt;/a&gt; accepts and returns fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gross_amount_minor
net_amount_minor
vat_amount_minor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the monetary representation remains integer-based throughout the core calculation path.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAT rates can use integers too
&lt;/h2&gt;

&lt;p&gt;VAT Engine represents rates using &lt;strong&gt;basis points&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19.00% = 1900 basis points
20.00% = 2000 basis points
7.00%  = 700 basis points
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A response therefore contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_rate_bps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of requiring calculation code to depend on a floating-point &lt;code&gt;0.19&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This gives the calculation contract explicit integer representations for both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;monetary amounts;&lt;/li&gt;
&lt;li&gt;VAT rates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rounding is part of the financial contract
&lt;/h2&gt;

&lt;p&gt;Even with the correct formula, VAT can produce fractions smaller than the currency's minor unit.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross: €9.99
VAT rate: 19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VAT extraction gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = 9.99 × 0.19 / 1.19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mathematical result contains more precision than EUR can represent.&lt;/p&gt;

&lt;p&gt;Eventually it needs to become cents.&lt;/p&gt;

&lt;p&gt;With deterministic rounding, a system can resolve this to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €8.39
VAT:   €1.60
Gross: €9.99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important question is not whether rounding happens.&lt;/p&gt;

&lt;p&gt;It must happen.&lt;/p&gt;

&lt;p&gt;The important question is &lt;strong&gt;where and how&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A risky architecture might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checkout      → rounding rule A
backend       → rounding rule B
report export → rounding rule C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each individual result may look reasonable while totals disagree by one or more minor units.&lt;/p&gt;

&lt;p&gt;A financial calculation API should therefore make rounding behavior part of a stable calculation contract.&lt;/p&gt;

&lt;p&gt;VAT Engine performs its core VAT arithmetic using integer amounts and deterministic rounding rather than delegating that decision to different callers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correct arithmetic does not determine the correct VAT rate
&lt;/h2&gt;

&lt;p&gt;There is another important separation.&lt;/p&gt;

&lt;p&gt;Even perfectly implemented VAT arithmetic cannot answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which rate applies to this product and transaction?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The arithmetic needs a rate as an input.&lt;/p&gt;

&lt;p&gt;Rate selection may depend on factors such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;destination country;&lt;/li&gt;
&lt;li&gt;product classification;&lt;/li&gt;
&lt;li&gt;transaction date;&lt;/li&gt;
&lt;li&gt;applicable tax treatment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why VAT Engine accepts a &lt;code&gt;tax_class_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax_class_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standard"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available classifications can be inspected through the public &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;Tax Classes API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A more realistic VAT calculation flow is therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Amount
+ amount basis
+ country
+ tax class
+ transaction date
↓
Rate selection
↓
VAT arithmetic
↓
Net + VAT + Gross
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Amount × percentage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The transaction date belongs in the calculation
&lt;/h2&gt;

&lt;p&gt;VAT is also time-dependent.&lt;/p&gt;

&lt;p&gt;Rates and tax treatment can change.&lt;/p&gt;

&lt;p&gt;A calculation for an older transaction should therefore not silently substitute today's rate simply because today's rate is easier to retrieve.&lt;/p&gt;

&lt;p&gt;VAT Engine accepts an explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-28"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and uses the requested date as part of rate selection.&lt;/p&gt;

&lt;p&gt;The underlying rate contract is described in the &lt;a href="https://vat-engine.app/docs/api/rates" rel="noopener noreferrer"&gt;VAT Rates API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is an important evidence distinction here.&lt;/p&gt;

&lt;p&gt;Older recorded lookup windows do not automatically prove the same thing as a reviewed, source-supported legal applicability interval.&lt;/p&gt;

&lt;p&gt;VAT Engine therefore distinguishes evidence states rather than assuming that any historical numeric match has identical provenance.&lt;/p&gt;

&lt;p&gt;That matters when a calculation is reviewed later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve calculation context, not just the final percentage
&lt;/h2&gt;

&lt;p&gt;Imagine seeing this six months later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT rate: 19%
VAT: €19.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You still do not know enough to understand how the result was produced.&lt;/p&gt;

&lt;p&gt;Useful calculation context includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;country;&lt;/li&gt;
&lt;li&gt;currency;&lt;/li&gt;
&lt;li&gt;tax class;&lt;/li&gt;
&lt;li&gt;transaction date;&lt;/li&gt;
&lt;li&gt;input amount;&lt;/li&gt;
&lt;li&gt;whether the input included VAT;&lt;/li&gt;
&lt;li&gt;selected rate;&lt;/li&gt;
&lt;li&gt;net amount;&lt;/li&gt;
&lt;li&gt;VAT amount;&lt;/li&gt;
&lt;li&gt;gross amount;&lt;/li&gt;
&lt;li&gt;calculation identity;&lt;/li&gt;
&lt;li&gt;rate evidence where available;&lt;/li&gt;
&lt;li&gt;calculation version where available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VAT Engine stores authenticated calculation history and exposes it through the &lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;Transactions API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is deliberately more useful than retaining only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rate = 19%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because the question during a later review is usually not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the VAT rate today?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What inputs and calculation context produced this particular result?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Keep calculation history separate from the business event
&lt;/h2&gt;

&lt;p&gt;There is also an architectural distinction worth preserving.&lt;/p&gt;

&lt;p&gt;Calling a tax calculator and recording a real sale are not necessarily the same event.&lt;/p&gt;

&lt;p&gt;A checkout might calculate VAT several times before the customer actually pays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cart updated
→ calculate

shipping country changed
→ calculate again

coupon applied
→ calculate again

customer pays
→ committed sale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treating every calculation request as a legal or reporting transaction would create a very different problem.&lt;/p&gt;

&lt;p&gt;VAT Engine therefore keeps &lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;calculation records&lt;/a&gt; separate from committed supply data used by its compliance and reporting workflows.&lt;/p&gt;

&lt;p&gt;This separation keeps an API calculation useful for debugging and audit context without pretending that every calculation represents a completed sale.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical implementation pattern
&lt;/h2&gt;

&lt;p&gt;For an ecommerce or SaaS application, keep the calculation boundary explicit.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;VatCalculationInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;priceIncludesVat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;taxClassId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transactionDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result can then be handled as structured financial data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;VatCalculationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;rateBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;netAmountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;vatAmountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grossAmountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important property is that the system never has to guess later whether:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;meant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€100.00 net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€100.00 gross
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That decision was explicit at the calculation boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common VAT calculation mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Multiplying a VAT-inclusive price directly by the VAT rate
&lt;/h3&gt;

&lt;p&gt;Wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = Gross × Rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = Gross × Rate / (1 + Rate)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Inferring whether the input includes VAT
&lt;/h3&gt;

&lt;p&gt;Make the amount basis explicit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is much safer than relying on assumptions somewhere else in the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Using floating point for monetary values
&lt;/h3&gt;

&lt;p&gt;Prefer integer minor units with a known currency scale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;€19.99 → 1999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than treating &lt;code&gt;19.99&lt;/code&gt; as a binary floating-point financial value.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Implementing different rounding rules in different services
&lt;/h3&gt;

&lt;p&gt;VAT arithmetic should have one deterministic rounding contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Treating the VAT rate as the entire tax decision
&lt;/h3&gt;

&lt;p&gt;Correct arithmetic still depends on selecting the relevant country, &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax class&lt;/a&gt;, rate data, and transaction date.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Saving only the final VAT amount
&lt;/h3&gt;

&lt;p&gt;Keep enough context to understand how the result was produced.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;Transactions API&lt;/a&gt; exists for exactly this kind of calculation history.&lt;/p&gt;

&lt;h2&gt;
  
  
  The math should be boring
&lt;/h2&gt;

&lt;p&gt;VAT arithmetic is not the most complicated part of VAT software.&lt;/p&gt;

&lt;p&gt;But it should be predictable.&lt;/p&gt;

&lt;p&gt;Before calculation starts, a system should be able to answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What amount was supplied?
Does it already include VAT?
Which currency is it in?
Which country applies?
Which tax class is being used?
What is the transaction date?
Which rate was selected?
How are fractional minor units rounded?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once those inputs are explicit, the arithmetic becomes deterministic.&lt;/p&gt;

&lt;p&gt;That is the approach behind the &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;VAT Engine calculation API&lt;/a&gt;: explicit price basis, integer minor-unit amounts, tax-class-aware rate selection, transaction-date context, and structured net, VAT, and gross outputs.&lt;/p&gt;

&lt;p&gt;You can test the arithmetic interactively with the &lt;a href="https://vat-engine.app/vat-calculator" rel="noopener noreferrer"&gt;free VAT calculator&lt;/a&gt;, browse the available &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax classes&lt;/a&gt;, or inspect the full &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;Calculate VAT API reference&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This article explains VAT calculation mechanics and software design. It is not tax or legal advice. The correct tax treatment of a transaction depends on its actual facts and the applicable rules.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>showdev</category>
      <category>saas</category>
    </item>
    <item>
      <title>EU VAT for SaaS &amp; Subscription Billing</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Fri, 18 Sep 2026 19:34:08 +0000</pubDate>
      <link>https://dev.to/vat-engine/eu-vat-for-saas-subscription-billing-3ho5</link>
      <guid>https://dev.to/vat-engine/eu-vat-for-saas-subscription-billing-3ho5</guid>
      <description>&lt;h1&gt;
  
  
  EU VAT for SaaS &amp;amp; Subscription Billing
&lt;/h1&gt;

&lt;p&gt;Selling SaaS subscriptions across the EU turns VAT into more than a simple percentage calculation.&lt;/p&gt;

&lt;p&gt;The billing flow may need to consider the customer's country, product tax treatment, whether the displayed price already includes VAT, the transaction date, refunds or corrections, and the information finance teams will need later for reporting.&lt;/p&gt;

&lt;p&gt;VAT Engine is designed to keep those pieces connected instead of forcing every SaaS product to maintain its own VAT tables and tax logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  EU VAT becomes part of the billing architecture
&lt;/h2&gt;

&lt;p&gt;A typical SaaS billing system is already responsible for subscriptions, renewals, invoices, payments, cancellations, and refunds.&lt;/p&gt;

&lt;p&gt;EU VAT adds another layer of decisions.&lt;/p&gt;

&lt;p&gt;For a transaction, your application may need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;which country the sale belongs to&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax class&lt;/a&gt; applies to the product&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which &lt;a href="https://vat-engine.app/docs/api/rates" rel="noopener noreferrer"&gt;VAT rate&lt;/a&gt; should be used&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;whether the amount is VAT-inclusive or VAT-exclusive&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;which transaction date should be considered&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and what context should be retained for later review&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A billing system that stores only something like &lt;code&gt;VAT rate: 19%&lt;/code&gt; loses much of the information needed to understand that result later.&lt;/p&gt;

&lt;p&gt;VAT Engine separates VAT calculation from the rest of the billing implementation while keeping the relevant transaction context available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculate VAT from structured transaction data
&lt;/h2&gt;

&lt;p&gt;A SaaS application can send transaction information to the VAT Engine REST API and receive a structured &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;VAT calculation&lt;/a&gt; result.&lt;/p&gt;

&lt;p&gt;Typical calculation inputs include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;destination country&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product tax class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction amount&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;currency&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT-inclusive or VAT-exclusive pricing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction date&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows your own application to remain responsible for subscriptions and payments while VAT-specific rate resolution and calculation logic is handled separately.&lt;/p&gt;

&lt;p&gt;A simplified flow looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;**Customer → SaaS billing flow → VAT Engine → VAT result → Payment → Transaction record**&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The API can be used from a custom checkout, subscription backend, billing service, or internal application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle VAT-inclusive and VAT-exclusive pricing correctly
&lt;/h2&gt;

&lt;p&gt;The same VAT rate produces different arithmetic depending on how a price is represented.&lt;/p&gt;

&lt;p&gt;If a subscription costs €100 before VAT and the applicable rate is 20%, VAT is added to the net amount.&lt;/p&gt;

&lt;p&gt;The result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net: €100
VAT: €20
Gross: €120
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if the customer-facing €100 price already includes 20% VAT, the VAT portion is not €20.&lt;/p&gt;

&lt;p&gt;It must be extracted from the gross amount.&lt;/p&gt;

&lt;p&gt;VAT Engine makes the pricing basis explicit so the calling application does not have to infer whether VAT should be added or extracted.&lt;/p&gt;

&lt;p&gt;This can be particularly useful when the same SaaS product serves different customer types, markets, or pricing models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the transaction date when working with older records
&lt;/h2&gt;

&lt;p&gt;Subscription systems do not only process transactions created today.&lt;/p&gt;

&lt;p&gt;Teams frequently need to work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;refunds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;corrections&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;imported historical transactions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;billing migrations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;reconciliation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;audit or accountant reviews&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using today's VAT rate for every historical operation can produce a result that does not match the original transaction.&lt;/p&gt;

&lt;p&gt;VAT Engine supports date-aware lookup using recorded rate windows where historical coverage is available.&lt;/p&gt;

&lt;p&gt;This means the transaction date can participate in VAT rate resolution instead of the system automatically assuming the currently active rate.&lt;/p&gt;

&lt;p&gt;Recorded lookup windows are kept distinct from a guarantee of legally verified historical applicability. Stronger source provenance and deterministic historical replay are areas being developed further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep enough context to understand the result later
&lt;/h2&gt;

&lt;p&gt;For financial systems, returning the correct number is only part of the job.&lt;/p&gt;

&lt;p&gt;Imagine someone reviews a subscription transaction several months later and asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why was this VAT treatment applied?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A percentage alone does not answer that question.&lt;/p&gt;

&lt;p&gt;A useful transaction record can retain context such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;country&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product tax class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction date&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;original amount&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;currency&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT-inclusive or VAT-exclusive pricing basis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT result&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source identity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;calculation timestamps&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;available rate context&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping this information together makes later reconciliation, refunds, reporting, and accountant review easier than reconstructing the original VAT decision from several disconnected systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support renewals and recurring billing
&lt;/h2&gt;

&lt;p&gt;Recurring subscription billing creates a new transaction each time a customer renews.&lt;/p&gt;

&lt;p&gt;Instead of permanently hard-coding VAT rates into a billing service, the application can resolve VAT using the transaction context relevant to each renewal.&lt;/p&gt;

&lt;p&gt;This provides a cleaner separation between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;subscription logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;payment processing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;VAT calculation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/transactions" rel="noopener noreferrer"&gt;transaction records&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/compliance" rel="noopener noreferrer"&gt;compliance preparation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also means VAT-related logic can evolve without requiring every billing workflow to maintain its own copy of tax data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle refunds and corrections with historical context
&lt;/h2&gt;

&lt;p&gt;Refunds are one of the places where transaction context becomes particularly important.&lt;/p&gt;

&lt;p&gt;A refund issued today may relate to a subscription transaction created months earlier.&lt;/p&gt;

&lt;p&gt;The original:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;transaction date&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;country&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product classification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;price basis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and recorded VAT context&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can all matter when reviewing how the refund should relate to the original sale.&lt;/p&gt;

&lt;p&gt;Keeping the original transaction record available is generally more useful than attempting to reconstruct everything from today's configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare transaction data for OSS workflows
&lt;/h2&gt;

&lt;p&gt;For B2C SaaS sales across EU countries, VAT calculation is only one part of the operational process.&lt;/p&gt;

&lt;p&gt;Finance or accounting teams eventually need an organized set of transactions that can be reviewed by reporting period, country, and source.&lt;/p&gt;

&lt;p&gt;VAT Engine provides compliance-oriented workflows around committed transaction records, including support for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OSS/IOSS preparation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction review&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;accountant-oriented exports&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;multi-source reporting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;reconciliation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VAT Engine does not submit VAT returns on behalf of the business and does not replace professional tax advice.&lt;/p&gt;

&lt;p&gt;The purpose is to make the underlying VAT data easier to organize, inspect, and prepare for downstream compliance work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep multiple billing sources organized
&lt;/h2&gt;

&lt;p&gt;As a SaaS business grows, revenue may start arriving through more than one system.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the main subscription checkout&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an additional storefront&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;marketplace sales&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;imported historical billing records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;internal payment flows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;connected commerce platforms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VAT Engine uses source profiles to keep transactions attributable to the system or sales channel they came from.&lt;/p&gt;

&lt;p&gt;That provides a consistent source model for reporting and reconciliation instead of requiring separate tax-data structures for every integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use SME threshold data alongside VAT workflows
&lt;/h2&gt;

&lt;p&gt;VAT obligations can also depend on the circumstances of the business itself.&lt;/p&gt;

&lt;p&gt;VAT Engine provides &lt;a href="https://vat-engine.app/docs/api/sme-thresholds" rel="noopener noreferrer"&gt;EU SME VAT threshold&lt;/a&gt; data that can be used alongside other compliance workflows, including values represented in EUR and relevant national currencies where supported.&lt;/p&gt;

&lt;p&gt;Keeping threshold information accessible through the same platform reduces the need to maintain another independent dataset inside the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where VAT Engine fits into a SaaS stack
&lt;/h2&gt;

&lt;p&gt;VAT Engine is not intended to replace your payment processor or subscription-management platform.&lt;/p&gt;

&lt;p&gt;Your existing systems can continue handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;subscriptions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;payment methods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;invoices&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;payment collection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cancellations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;customer accounts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VAT Engine provides the VAT-specific layer around those workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;EU VAT calculation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT rate resolution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product tax classes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;date-aware rate lookup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source profiles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/sme-thresholds" rel="noopener noreferrer"&gt;SME VAT&lt;/a&gt; threshold data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/compliance#oss--ioss-tax-registrations" rel="noopener noreferrer"&gt;OSS/IOSS&lt;/a&gt; preparation workflows&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The REST API can be integrated into an existing architecture without requiring VAT Engine to become the system responsible for the entire billing lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: a new B2C subscription
&lt;/h3&gt;

&lt;p&gt;Consider a SaaS customer purchasing a subscription from another EU country.&lt;/p&gt;

&lt;p&gt;The billing application collects the required transaction context and sends the relevant data to VAT Engine.&lt;/p&gt;

&lt;p&gt;VAT Engine resolves the recorded VAT rate and calculates the VAT amounts.&lt;/p&gt;

&lt;p&gt;The billing application can then use the result when presenting or processing the transaction.&lt;/p&gt;

&lt;p&gt;After the sale, the transaction context can be retained for reporting and later review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: a subscription renewal
&lt;/h3&gt;

&lt;p&gt;When the subscription renews, the billing service creates a new transaction.&lt;/p&gt;

&lt;p&gt;The current transaction context can be evaluated again rather than assuming that every renewal must reuse tax data stored when the customer originally subscribed.&lt;/p&gt;

&lt;p&gt;This keeps recurring billing logic separate from VAT rate maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: refunding an older subscription
&lt;/h3&gt;

&lt;p&gt;A refund may relate to a transaction that occurred during an earlier reporting period.&lt;/p&gt;

&lt;p&gt;The original transaction date and recorded VAT context provide a better basis for reviewing the refund than simply applying today's rate.&lt;/p&gt;

&lt;p&gt;Date-aware lookup and retained transaction records help support that workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: migrating historical billing data
&lt;/h3&gt;

&lt;p&gt;A SaaS company moving from another billing architecture may need to import older transactions.&lt;/p&gt;

&lt;p&gt;Historical records can be organized using consistent source profiles, tax classes, countries, dates, and recorded VAT context.&lt;/p&gt;

&lt;p&gt;That creates a more structured foundation for reconciliation and future reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this use case is for
&lt;/h2&gt;

&lt;p&gt;VAT Engine can be useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SaaS businesses selling subscriptions across EU countries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;developers building custom subscription billing systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;B2C digital service providers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;finance teams preparing OSS data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;platforms that need VAT calculation through an API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;teams reconciling or migrating historical billing data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keep VAT logic outside your billing code
&lt;/h2&gt;

&lt;p&gt;A SaaS billing system already has enough responsibilities.&lt;/p&gt;

&lt;p&gt;Maintaining VAT rates, tax classifications, historical rate context, and compliance-oriented transaction records does not necessarily need to become another permanent part of that codebase.&lt;/p&gt;

&lt;p&gt;VAT Engine provides a dedicated EU VAT layer that can be integrated through the API while keeping the transaction context needed for later review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;**VAT Engine is currently available in free alpha. No payment method is required.**&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>shopify</category>
      <category>software</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why a VAT API Needs More Than a Rate Lookup</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Fri, 18 Sep 2026 19:27:11 +0000</pubDate>
      <link>https://dev.to/vat-engine/why-a-vat-api-needs-more-than-a-rate-lookup-50i9</link>
      <guid>https://dev.to/vat-engine/why-a-vat-api-needs-more-than-a-rate-lookup-50i9</guid>
      <description>&lt;p&gt;When developers first add VAT to an ecommerce or SaaS product, the problem can look surprisingly small:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Find the customer's country.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Look up the VAT rate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiply the price by that percentage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Done.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That works until the first real edge cases arrive.&lt;/p&gt;

&lt;p&gt;What product was sold? Was the displayed price VAT-inclusive? Which country actually has taxing rights? Was this transaction created today or six months ago? Is it a refund of an older sale? Which rate was used when the original transaction happened?&lt;/p&gt;

&lt;p&gt;At that point, a VAT integration stops being a percentage lookup and starts becoming a data and decision-traceability problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  A country does not have one VAT rate
&lt;/h2&gt;

&lt;p&gt;A basic VAT table might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"FR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"IT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful, but incomplete.&lt;/p&gt;

&lt;p&gt;EU member states can apply standard, reduced, zero, and other rates depending on the type of supply. Books, accommodation, food, pharmaceuticals, and other categories may receive different treatment.&lt;/p&gt;

&lt;p&gt;So the more useful question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is Germany's VAT rate?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which VAT rate applies in Germany to this product category for this transaction?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means a useful API needs product tax context, not only a country code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transaction date matters
&lt;/h2&gt;

&lt;p&gt;Using today's rate for every calculation creates another problem.&lt;/p&gt;

&lt;p&gt;Imagine processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;a refund for an order placed last year,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an imported historical transaction,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an audit of an older sale,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a correction to previously recorded data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rate that is active today is not necessarily the rate that was used for the original transaction.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://vat-engine.app/docs" rel="noopener noreferrer"&gt;VAT API&lt;/a&gt; therefore benefits from accepting an explicit transaction or supply date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response can then identify the recorded rate window used for that lookup.&lt;/p&gt;

&lt;p&gt;One important distinction: a recorded historical window and a legally verified effective date are not automatically the same thing.&lt;/p&gt;

&lt;p&gt;If the underlying source data does not prove legal applicability for a historical period, the API should make that limitation explicit rather than pretending to know more than it does.&lt;/p&gt;

&lt;p&gt;Failing clearly is much safer than silently returning a convenient number.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAT-inclusive and VAT-exclusive prices are different calculations
&lt;/h2&gt;

&lt;p&gt;There is also a basic arithmetic distinction that often gets buried inside tax code.&lt;/p&gt;

&lt;p&gt;If €100 is VAT-exclusive at 20%, the total is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net:   €100
VAT:    €20
Gross: €120
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if €100 already includes 20% VAT, the VAT portion is not €20.&lt;/p&gt;

&lt;p&gt;It is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAT = 100 - (100 / 1.20)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is approximately €16.67.&lt;/p&gt;

&lt;p&gt;An API should therefore require the caller to make the price basis explicit instead of guessing.&lt;/p&gt;

&lt;p&gt;For financial calculations, I also prefer integer minor units rather than floating-point money:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_includes_vat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;10000&lt;/code&gt; represents €100.00.&lt;/p&gt;

&lt;p&gt;That avoids a whole class of floating-point rounding problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Returning the correct number is only half the job
&lt;/h2&gt;

&lt;p&gt;Suppose an API returns this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vat_amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1597&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six months later, someone asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why did this transaction use 19% VAT?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The number alone cannot answer that.&lt;/p&gt;

&lt;p&gt;A more useful calculation record may also retain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;country&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product tax class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction date&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAT-inclusive/exclusive basis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;input amount&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rate used&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source/store identity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;calculation timestamp&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;relevant rate-data version or provenance where available&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns the VAT result from a disposable API response into something that can actually be reviewed later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source identity matters in multi-store commerce
&lt;/h2&gt;

&lt;p&gt;The problem becomes more visible when a business has several sales channels.&lt;/p&gt;

&lt;p&gt;An order might come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/integrations/shopify" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a custom checkout&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;another marketplace&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an imported CSV&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an internal billing system&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For reporting purposes, simply storing "transaction 12345" may not be enough.&lt;/p&gt;

&lt;p&gt;The system also needs to know which source owns that transaction.&lt;/p&gt;

&lt;p&gt;That is why I think source profiles are useful as a first-class concept rather than just another string attached to an order.&lt;/p&gt;

&lt;p&gt;They make it possible to group and reconcile records without losing where those records originally came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance workflows need committed data
&lt;/h2&gt;

&lt;p&gt;There is another important boundary between a calculator and a compliance system.&lt;/p&gt;

&lt;p&gt;Someone calling a VAT calculator ten times should not automatically create ten filing records.&lt;/p&gt;

&lt;p&gt;Reporting workflows need a defined population of committed business transactions, not every exploratory API request ever made.&lt;/p&gt;

&lt;p&gt;That separation becomes important for OSS/IOSS preparation, accountant review, exports, and reconciliation.&lt;/p&gt;

&lt;p&gt;Calculation and compliance can share the same data model, but they should not be treated as the same action.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am building around these ideas
&lt;/h2&gt;

&lt;p&gt;These problems are what pushed VAT Engine beyond a simple VAT-rate endpoint.&lt;/p&gt;

&lt;p&gt;The current direction combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;EU VAT &lt;a href="https://vat-engine.app/docs/api/calculate" rel="noopener noreferrer"&gt;calculation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TEDB-backed &lt;a href="https://vat-engine.app/docs/api/rates" rel="noopener noreferrer"&gt;rate data&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;product &lt;a href="https://vat-engine.app/docs/api/tax-classes" rel="noopener noreferrer"&gt;tax classes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;date-aware rate lookup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;source profiles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SME VAT threshold data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vat-engine.app/docs/api/compliance" rel="noopener noreferrer"&gt;OSS/IOSS&lt;/a&gt; preparation workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ecommerce integrations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is still more work to do, particularly around stronger rate provenance, immutable source evidence, and deterministic replay.&lt;/p&gt;

&lt;p&gt;The long-term goal is not just to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What VAT rate should I use?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is to make it possible to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What information produced this VAT result, and can I understand that decision later?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For financial infrastructure, I think that second question is ultimately the more interesting one.&lt;/p&gt;

</description>
      <category>api</category>
      <category>saas</category>
      <category>vat</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>What Building a Shopify Integration Taught Me About Trusting External APIs</title>
      <dc:creator>Vasyl Kyryliuk</dc:creator>
      <pubDate>Sat, 01 Aug 2026 17:31:28 +0000</pubDate>
      <link>https://dev.to/vat-engine/what-building-a-shopify-integration-taught-me-about-trusting-external-apis-3hbm</link>
      <guid>https://dev.to/vat-engine/what-building-a-shopify-integration-taught-me-about-trusting-external-apis-3hbm</guid>
      <description>&lt;p&gt;Building a Shopify integration looks straightforward at first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate the store.&lt;/li&gt;
&lt;li&gt;Request orders.&lt;/li&gt;
&lt;li&gt;Transform the response.&lt;/li&gt;
&lt;li&gt;Store the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difficult part begins when the integration needs to become reliable enough for production.&lt;/p&gt;

&lt;p&gt;While building the Shopify integration for VAT Engine, I learned that consuming an external API is not only about parsing its response. It is about defining exactly what you trust, what you verify, and what must fail closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  External APIs are contracts, not just endpoints
&lt;/h2&gt;

&lt;p&gt;A GraphQL query can continue returning a successful response while the meaning of the returned data changes around it.&lt;/p&gt;

&lt;p&gt;An integration depends on more than the endpoint itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API version&lt;/li&gt;
&lt;li&gt;requested fields&lt;/li&gt;
&lt;li&gt;required access scopes&lt;/li&gt;
&lt;li&gt;pagination behaviour&lt;/li&gt;
&lt;li&gt;nullable fields&lt;/li&gt;
&lt;li&gt;partial GraphQL errors&lt;/li&gt;
&lt;li&gt;protected customer-data permissions&lt;/li&gt;
&lt;li&gt;webhook topics&lt;/li&gt;
&lt;li&gt;authentication requirements&lt;/li&gt;
&lt;li&gt;assumptions made during transformation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That entire combination is the real contract.&lt;/p&gt;

&lt;p&gt;If any part changes silently, the integration may continue running while producing incomplete or misleading results.&lt;/p&gt;

&lt;p&gt;For a tax-related system, that is worse than a visible failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make every dependency explicit
&lt;/h2&gt;

&lt;p&gt;We created a source-contract matrix for the Shopify integration.&lt;/p&gt;

&lt;p&gt;For every operation, it records information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the GraphQL operation name&lt;/li&gt;
&lt;li&gt;the Shopify API version&lt;/li&gt;
&lt;li&gt;required scopes&lt;/li&gt;
&lt;li&gt;fields used by the application&lt;/li&gt;
&lt;li&gt;validation gates&lt;/li&gt;
&lt;li&gt;evidence limitations&lt;/li&gt;
&lt;li&gt;associated tests&lt;/li&gt;
&lt;li&gt;downstream consumers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns undocumented assumptions into something reviewable.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The order query gives us everything we need.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system has to answer more precise questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which order fields are required?&lt;/li&gt;
&lt;li&gt;Which fields are optional?&lt;/li&gt;
&lt;li&gt;What happens when Shopify returns partial GraphQL errors?&lt;/li&gt;
&lt;li&gt;Can a missing duties field be interpreted as zero?&lt;/li&gt;
&lt;li&gt;Which evidence is sufficient to classify a transaction?&lt;/li&gt;
&lt;li&gt;Which missing evidence must send the order to manual review?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Missing data is not the same as zero
&lt;/h2&gt;

&lt;p&gt;This was one of the most important design rules.&lt;/p&gt;

&lt;p&gt;Imagine that Shopify returns an order but cannot return duties or additional-fee information for part of the query.&lt;/p&gt;

&lt;p&gt;There are at least three possible meanings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The amount is genuinely zero.&lt;/li&gt;
&lt;li&gt;The field is unavailable.&lt;/li&gt;
&lt;li&gt;Shopify returned a partial error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Treating all three cases as zero creates false confidence.&lt;/p&gt;

&lt;p&gt;A safer model distinguishes between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confirmed_zero
known_value
unavailable
conflicting
needs_review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is useful far beyond VAT.&lt;/p&gt;

&lt;p&gt;It applies to payments, inventory, shipping, identity verification, analytics, and almost any integration where incomplete data can affect a business decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate the contract in CI
&lt;/h2&gt;

&lt;p&gt;Documentation alone eventually becomes outdated.&lt;/p&gt;

&lt;p&gt;The next step was making the contract machine-readable and validating it during CI.&lt;/p&gt;

&lt;p&gt;The validation checks for problems such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an operation missing from the inventory&lt;/li&gt;
&lt;li&gt;an undocumented validation gate&lt;/li&gt;
&lt;li&gt;malformed contract data&lt;/li&gt;
&lt;li&gt;unsafe file references&lt;/li&gt;
&lt;li&gt;claims that exceed the available evidence&lt;/li&gt;
&lt;li&gt;drift between queries, tests, and documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to prove that an external API will never change.&lt;/p&gt;

&lt;p&gt;The goal is to make our own assumptions visible and make accidental drift difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization must be tested as an attacker would use it
&lt;/h2&gt;

&lt;p&gt;A valid Shopify session token is only the beginning.&lt;/p&gt;

&lt;p&gt;An embedded application also needs to defend against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;forged tokens&lt;/li&gt;
&lt;li&gt;expired tokens&lt;/li&gt;
&lt;li&gt;tokens that are not valid yet&lt;/li&gt;
&lt;li&gt;wrong audience values&lt;/li&gt;
&lt;li&gt;tokens issued for another shop&lt;/li&gt;
&lt;li&gt;cross-tenant resource access&lt;/li&gt;
&lt;li&gt;resource enumeration&lt;/li&gt;
&lt;li&gt;mutation replay&lt;/li&gt;
&lt;li&gt;oversized request bodies&lt;/li&gt;
&lt;li&gt;unsafe proxy destinations&lt;/li&gt;
&lt;li&gt;sensitive information leaking into logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many authorization tests verify only the successful path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valid user + valid token + owned resource = success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more valuable tests cover combinations that must fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valid user + valid token + another store's resource = rejected
valid token + wrong audience = rejected
expired token + valid resource = rejected
replayed mutation = rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multi-tenant security is not complete until ownership is checked at every relevant boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks require a different trust model
&lt;/h2&gt;

&lt;p&gt;Webhook endpoints are public by design.&lt;/p&gt;

&lt;p&gt;That means their processing order matters.&lt;/p&gt;

&lt;p&gt;A safer flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the bounded raw request body.&lt;/li&gt;
&lt;li&gt;Verify the HMAC signature.&lt;/li&gt;
&lt;li&gt;Reject invalid requests.&lt;/li&gt;
&lt;li&gt;Parse the payload.&lt;/li&gt;
&lt;li&gt;Resolve the relevant integration.&lt;/li&gt;
&lt;li&gt;Apply idempotency.&lt;/li&gt;
&lt;li&gt;Persist minimal required information.&lt;/li&gt;
&lt;li&gt;Queue expensive processing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Parsing or resolving tenant data before signature verification creates unnecessary exposure.&lt;/p&gt;

&lt;p&gt;Webhook retries also mean that duplicate delivery is normal, not exceptional. Idempotency must therefore be part of the design from the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail closed, but preserve recoverability
&lt;/h2&gt;

&lt;p&gt;Failing closed does not have to mean permanently losing the transaction.&lt;/p&gt;

&lt;p&gt;When evidence is incomplete, the system can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retain a minimal diagnostic record&lt;/li&gt;
&lt;li&gt;place the transaction into a review queue&lt;/li&gt;
&lt;li&gt;allow a controlled replay&lt;/li&gt;
&lt;li&gt;run a reconciliation job later&lt;/li&gt;
&lt;li&gt;preserve the reason why automated processing stopped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provides a useful balance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do not silently accept uncertain data&lt;/li&gt;
&lt;li&gt;do not discard potentially recoverable data&lt;/li&gt;
&lt;li&gt;do not hide the decision from operators&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would do earlier next time
&lt;/h2&gt;

&lt;p&gt;If I were starting another integration today, I would define these before implementing the first production query:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A machine-readable inventory of external operations.&lt;/li&gt;
&lt;li&gt;Explicit trust boundaries for each response field.&lt;/li&gt;
&lt;li&gt;A distinction between zero, missing, unavailable, and conflicting data.&lt;/li&gt;
&lt;li&gt;Cross-tenant authorization abuse tests.&lt;/li&gt;
&lt;li&gt;Webhook idempotency and reconciliation.&lt;/li&gt;
&lt;li&gt;Evidence-based review states instead of optimistic defaults.&lt;/li&gt;
&lt;li&gt;CI checks that detect contract drift.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These decisions are much cheaper to make early than after customers depend on the integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;A production integration is not simply a connector between two APIs.&lt;/p&gt;

&lt;p&gt;It is a boundary between two systems with different data models, security assumptions, failure modes, and release cycles.&lt;/p&gt;

&lt;p&gt;The safest approach is to treat every external value as evidence that must be verified, classified, and preserved, not merely as JSON that successfully parsed.&lt;/p&gt;

&lt;p&gt;I am applying these principles while building the Shopify integration for VAT Engine, an EU VAT compliance platform. Shopify is the first native commerce connector, with additional ecommerce integrations planned on the same shared ingestion model.&lt;/p&gt;

&lt;p&gt;I would be interested to hear how other developers prevent external API contract drift in production integrations.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>api</category>
      <category>security</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
