Calculate VAT
Calculate EU VAT with explicit amount basis, optional idempotency, resolved transaction dates, and rate and calculation evidence.
Endpoint
Treatment results depend on applicable reviewed coverage, not merely a matching CN/CPA code or rate. Unresolved source conditions remain reviewable uncertainty; they are not silently replaced by a broader rate. Historical stored decisions retain their original evidence when reviewed interpretations change.
POST /v1/vat/calculateRequired scope: full or calculate-only
Optional Headers
| Header | Type | Description |
|---|---|---|
X-Source-ID | string | Optional headless storefront or custom checkout tag for reporting. Not required for calculation. |
Idempotency-Key | string | Optional authenticated retry key, 1-255 visible ASCII characters. Requires an explicit transaction_date. |
X-Source-ID does not affect the VAT rate, net amount, VAT amount, or gross amount. It is captured with the logged VAT calculation event when transaction logging is enabled, so later reports can filter or export rows by checkout/source. Use a stable, non-sensitive source tag from 1 to 100 characters using letters, digits, _, -, or / (for example headless-checkout, custom-store/eu, or mobile-checkout). Invalid values return 400 invalid_source_id. Do not put customer names, emails, order numbers, API credentials, or other personal/secrets data in this header.
Use Idempotency-Key when a client may retry the same authenticated calculation. The same key and
canonical request and source tag returns the original calculation; reusing it for a different
request body or normalized X-Source-ID returns
409 idempotency_conflict. The mapping remains valid for the lifetime of that calculation. Do not
put personal data, credentials, or business payloads in the key.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
country | string | Yes | ISO 3166-1 alpha-2 code (e.g., DE, FR, IT) |
currency | string | Yes | ISO 4217 code (e.g., EUR, USD) |
tax_class_id | string | Yes | Tax class identifier (e.g., standard, books, food) |
gross_amount_minor | integer | Yes | Input amount in minor currency units (e.g., €119.00 = 11900) |
price_includes_vat | boolean | Yes | Send explicitly: true for a gross input; false for a net input |
transaction_date | string | No | RFC 3339 or YYYY-MM-DD format. Defaults to current date. |
Example Request
curl -X POST https://api.vat-engine.app/v1/vat/calculate \-H "X-API-Key: YOUR_API_KEY" \-H "Idempotency-Key: checkout-attempt-7f49" \-H "Content-Type: application/json" \-d '{ "country": "DE", "currency": "EUR", "gross_amount_minor": 11900, "price_includes_vat": true, "tax_class_id": "standard", "transaction_date": "2026-01-28"}'To tag the logged calculation for multi-store or multi-channel reports, add the optional source header:
-H "X-Source-ID: headless-checkout-eu"Response
{
"calculation_id": "e2d0940e-b123-47b7-80ad-8a3673fa4680",
"country": "DE",
"currency": "EUR",
"vat_rate_bps": 1900,
"gross_amount_minor": 11900,
"net_amount_minor": 10000,
"vat_amount_minor": 1900,
"transaction_date": "2026-01-28T00:00:00Z",
"rate_provenance": {
"schema_version": "rate-evidence/v1",
"evidence_status": "legacy_unverifiable",
"source": "legacy_recorded_window",
"effective_at": "2026-01-28T00:00:00Z",
"source_evidence": []
},
"calculation_provenance": {
"schema_version": "calculation-evidence/v1",
"calculation_version": "vat-calculator-half-up/v1",
"execution_digest": "...",
"algorithm_manifest_version": "vat-algorithm-manifest/v1",
"algorithm_manifest_digest": "...",
"ownership": "tenant_owned",
"evidence_status": "captured",
"replay_status": "ineligible"
}
}Reviewed-rate responses additionally include their approved version, mapping and row digests, and one or more bounded public-safe snapshot references. Use the OpenAPI schema for the complete response contract.
Response Fields
| Field | Type | Description |
|---|---|---|
calculation_id | string | null | Stored transaction UUID. Null only for an unowned compatibility response that is not persisted. |
country | string | ISO country code |
currency | string | ISO currency code |
vat_rate_bps | integer | VAT rate in basis points (1900 = 19.00%) |
gross_amount_minor | integer | Gross amount in minor units |
net_amount_minor | integer | Net amount (excluding VAT) in minor units |
vat_amount_minor | integer | VAT amount in minor units |
transaction_date | string | Resolved UTC transaction time |
rate_provenance | object | Evidence status and reviewed or legacy rate-source references |
calculation_provenance | object | Ownership, captured-evidence state, calculation version, and replay status |
Notes
- All amounts are in minor currency units (cents) to avoid floating-point errors.
- VAT rates are in basis points (1/100th of a percent). 1900 bps = 19.00%.
- When
price_includes_vatistrue, the API extracts VAT from the gross amount. Whenfalse, it adds VAT on top. - Send
price_includes_vatexplicitly, includingfalsefor net input. Omitted ornullvalues return400 missing_price_includes_vat. - Date-only input resolves to midnight UTC. When
Idempotency-Keyis present, omittingtransaction_datereturns400 transaction_date_required_for_idempotency. calculation_idis the same UUID exposed asidby the Transactions API; VAT Engine does not create a second calculation identity.capturedevidence does not by itself make a calculation replayable. Only a record whose transaction detail reportsreplay_status: availablecan use the exact replay endpoint.execution_digestidentifies the calculation-version contract; it is not a hash of executable code.algorithm_manifest_digestbinds the compile-time registered arithmetic, validation, currency-scale, rounding, and frozen-fixture contract. Calculation records store the manifest version and digest rather than a separate copy of the manifest.X-Source-IDis optional reporting metadata, but it is part of idempotent request identity so a retry cannot silently change the stored calculation's source attribution. Omit it if you do not need store/channel filtering in transaction reports.- Managed source profiles can also register the same source key through
POST /v1/sources, but the header itself stays a raw stable integration tag. - This endpoint records calculation history only. It does not add a sale to OSS threshold monitoring, Filing Prep, or other compliance reports.
- After a governed treatment release is activated, v1 uses it only when the request is sufficient
for an explicitly context-free numeric sales-VAT rule. It never assumes customer type,
establishment, dispatch, liability, or a product classification. If a relevant exception cannot
be resolved from v1 inputs, the endpoint returns
422with guidance to use v2.
New authenticated calculations retain the available reviewed-rate lineage and calculation evidence
before success is returned. Historical or fallback rate windows remain explicitly
legacy_unverifiable; VAT Engine does not assign provenance merely because their numeric rate
matches a reviewed rate. Calculations backed by complete sealed reviewed-rate evidence can report
replay_status: available and use exact
replay. Caller-selected historical
alternatives are counterfactual simulations and are not accepted by the production calculation or
replay endpoints.
Governed component-aware calculation (v2)
POST /v2/vat/calculateRequired scope: full or calculate-only
Required header: Idempotency-Key
The v2 endpoint evaluates four obligations independently: sales VAT, import VAT, customs duty, and
fees. It preserves jurisdiction, liable party, collection point, amount status, legal basis, and
provenance for each component. A component that cannot be established remains incomplete or
unsupported; “outside scope here” never means “no tax anywhere.”
country is your jurisdiction assertion. If treatment_context.destination_country disagrees,
the response is a durable 409 conflicting decision. A supplied catalog edition must already be
part of the production treatment release selected for your tenant; callers cannot use it to bypass
cutover authority.
curl -X POST https://api.vat-engine.app/v2/vat/calculate -H "X-API-Key: YOUR_API_KEY" -H "Idempotency-Key: order-2026-1042-tax-v1" -H "Content-Type: application/json" -d '{ "country": "DE", "currency": "EUR", "amount_minor": 10000, "price_includes_vat": false, "transaction_date": "2026-06-01", "classification": { "scheme": "cn", "code": "49090000", "catalog_version": "2026" }, "treatment_context": { "customer_type": "b2c", "supplier_establishment_country": "DE", "dispatch_country": "DE", "destination_country": "DE" }}'For a tax-class-only evaluation, replace classification with the reviewed class used by your
integration:
curl -X POST https://api.vat-engine.app/v2/vat/calculate -H "X-API-Key: YOUR_API_KEY" -H "Idempotency-Key: order-2026-1043-tax-v1" -H "Content-Type: application/json" -d '{ "country": "DE", "currency": "EUR", "amount_minor": 10000, "price_includes_vat": false, "transaction_date": "2026-06-01", "tax_class_id": "standard"}'When both selectors are supplied, VAT Engine verifies their governed mapping. A missing or
different mapping is retained as a durable 409 classification_conflict; one selector never
silently overrides the other.
For an authorized managed source profile, add its dedicated selector. The selected immutable revision is sealed into the decision, but ordinary profile defaults are configuration—not date-bounded legal evidence—and therefore cannot decide a legal predicate. The selector does not grant connector identity, verified confidence, or a different rollout:
-H "X-Evidence-Profile: storefront-eu"The profile must be active and owned by the authenticated tenant. Unknown, foreign, inactive, or
ambiguous selectors produce a durable 422 evidence_profile_unavailable decision. X-Source-ID
remains reporting metadata and never selects this profile or a rollout.
Conflict example
This request makes DE the asserted jurisdiction but supplies FR as its destination. The
conflict is persisted so an identical retry returns the same decision:
curl -X POST https://api.vat-engine.app/v2/vat/calculate -H "X-API-Key: YOUR_API_KEY" -H "Idempotency-Key: order-2026-1044-conflict-v1" -H "Content-Type: application/json" -d '{ "country": "DE", "currency": "EUR", "amount_minor": 10000, "price_includes_vat": false, "transaction_date": "2026-06-01", "treatment_context": { "destination_country": "FR" }}'{
"status": "conflicting",
"components": [
{
"kind": "sales_vat",
"treatment": null,
"treatment_status": "conflicting",
"amount_status": "conflicting",
"findings": ["jurisdiction_conflict"]
}
]
}The actual response includes all required component fields and exactly four components.
Unsupported example
The same endpoint fails closed when no activated treatment release covers the asserted jurisdiction and legal date:
curl -X POST https://api.vat-engine.app/v2/vat/calculate -H "X-API-Key: YOUR_API_KEY" -H "Idempotency-Key: order-2026-1045-unsupported-v1" -H "Content-Type: application/json" -d '{ "country": "DE", "currency": "EUR", "amount_minor": 10000, "price_includes_vat": false, "transaction_date": "2026-06-01"}'The durable response uses HTTP 422, aggregate status unsupported, and the finding
treatment_coverage_unavailable. This outcome is different from an operational 503, which is
not cached as a business decision.
The same idempotency key and canonical request returns the original stored decision, even after a
cutover. Reusing the key with changed input returns 409 idempotency_conflict. After remediation,
use a new key and optionally pass the tenant-owned supersedes_decision_id. Retry a transient 503
with the same key; a lost response does not prove the database rolled back.
{
"decision_id": "7c0f4165-fb85-47c6-9594-2e623535ada9",
"schema_version": "governed-calculation/v2",
"status": "partial",
"components": [
{
"kind": "sales_vat",
"treatment": "taxable",
"treatment_status": "resolved",
"amount_status": "calculated",
"jurisdiction": "DE",
"liable_party": "merchant",
"collection_point": "checkout",
"rate_category": "reduced",
"vat_rate_bps": 700,
"amount_minor": 700,
"currency": "EUR",
"calculation_id": "ab9c7869-84a4-4cce-93dc-f8ae1c5f8362",
"legal_basis": "EU TEDB VAT rate evidence",
"provenance": ["treatment-release:…", "rule:…"],
"deduction_status": "unknown",
"findings": []
},
{
"kind": "import_vat",
"treatment": null,
"treatment_status": "unsupported",
"amount_status": "unsupported",
"rate_category": null,
"vat_rate_bps": null,
"amount_minor": null,
"currency": "EUR",
"provenance": [],
"deduction_status": "unknown",
"findings": ["unsupported_scope"]
}
],
"legal_at": "2026-06-01T00:00:00Z",
"knowledge_at": "2026-09-06T20:00:00Z",
"resolver_version": "governed-treatment/v2",
"findings": []
}The real response always contains exactly four components; the shortened example shows two. 200
and partial do not grant filing permission. Filing consumers must require the exact obligation,
amount, liability, collection point, registration, route, and current-authority evidence they need.
409 decisions retain component conflicts; 422 decisions retain incomplete or unsupported
findings with a durable decision ID.
| Aggregate status | Meaning |
|---|---|
complete | Every obligation has a calculated amount, an evidence-proven zero, or an evidence-proven not_applicable outcome. |
partial | At least one obligation is complete and no component conflicts, while another remains incomplete or unsupported. |
incomplete | No obligation is complete and at least one requires additional supported facts or authority. |
conflicting | At least one component contains mutually inconsistent evidence or selectors. |
unsupported | No obligation is complete and the required legal flow or authority is not implemented. |
The initial governed release builder publishes sales-VAT rules only. Import VAT, customs duty, and
fees therefore remain explicit unsupported components, so current deployments normally return
partial, incomplete, conflicting, or unsupported. The complete contract is reserved for a
future reviewed release that establishes all four obligations; VAT Engine does not fabricate a
complete example from missing authority.
Shopify
Learn how to connect Shopify to VAT Engine, link your account, review permissions, sync orders, and manage the integration from Shopify Admin or VAT Engine.
VAT Rates
Reference for GET /v1/vat/rates, including country and tax-class parameters, recorded-window lookups by date, and example rate responses.