Tax Calculations
The Tax Calculation API determines the correct tax amount for any transaction — VAT, GST, sales tax, and other indirect taxes — across 100+ countries in a single API call. You send a seller, a customer, and a list of line items; Clearvo resolves the applicable jurisdiction, classifies each product, applies the correct rate band, and returns a fully broken-down tax result.
Key capabilities
- 100+ countries — EU multi-band VAT (standard, reduced, second-reduced, super-reduced), US sales tax (all 50 states, 13k+ jurisdictions), Canadian GST/HST/PST, and more
- AI-powered product classification — describe a product in plain language; no HS codes or custom slug mapping required
- Jurisdiction resolution — determines where tax is owed based on shipping address, billing address, IP geolocation, or card BIN country (2-of-3 rule for B2C digital goods)
- B2B reverse charge and EU IOSS — applies the correct treatment automatically based on seller and buyer tax IDs
- EN16931 tax codes — output maps directly to Clearvo e-invoicing fields with no additional transformation
- Flexible input — send country names, ISO alpha-3 codes, currency names, US state names, or informal customer type labels; all normalised server-side before validation
- Graceful degradation — never errors on rate-service failures; returns a result with
degraded: trueand a zero rate - Ephemeral and committed calculations — calculate without recording (for quoting), or commit the result to your audit trail
https://api.clearvo.io/v1) and the same csk_live_* / csk_test_* key system. No separate credentials are needed — the same key you use for e-invoicing and TIN validation works here.Authentication
All Tax Calculation endpoints require an x-api-key header. Both entity-scoped and account-scoped keys are accepted on all endpoints. Account-scoped keys must include an X-Entity-Id header on every request to identify the target entity — entity-scoped keys do not require this header.
x-api-key: csk_live_...
Use a csk_test_* key for sandbox requests — rate lookups and jurisdiction resolution run normally, but no external rate-authority calls are made and results are not billed. See the Sandbox section for details.
Quickstart
The simplest possible calculation: a B2C digital product sold to a customer in Germany. Clearvo classifies the product, resolves the jurisdiction, and returns the German VAT rate (19%) with an EN16931 tax code ready for e-invoicing.
curl https://api.clearvo.io/v1/tax/calculate \
-X POST \
-H "x-api-key: csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"seller": {
"country": "IE",
"taxId": "IE1234567T"
},
"customer": {
"address": { "country": "DE" }
},
"lineItems": [
{
"id": "line-1",
"amount": 100.00,
"productName": "Annual SaaS subscription"
}
]
}'
{
"calculationId": "cl_calc_01j4...",
"committed": false,
"sandbox": false,
"degraded": false,
"currency": "EUR",
"jurisdiction": {
"country": "DE",
"region": null,
"method": "BILLING_ADDRESS",
"precision": "COUNTRY",
"conflicts": []
},
"treatment": {
"taxTreatment": "STANDARD",
"taxCode": "S",
"rate": 0.19,
"rateBand": "STANDARD"
},
"lineItems": [
{
"id": "line-1",
"taxCode": "S",
"rate": 0.19,
"taxableAmount": 100.00,
"taxAmount": 19.00,
"totalAmount": 119.00,
"rateBand": "STANDARD",
"classification": {
"slug": "digital-services",
"confidence": 0.97,
"status": "APPROVED",
"fromCache": false
}
}
],
"totals": {
"subtotal": 100.00,
"totalTax": 19.00,
"total": 119.00
},
"rateSource": {
"provider": "CLEARVO",
"asOf": "2026-06-21",
"cacheHit": true
}
}
The taxCode: "S" and rate: 0.19 on each line item flow directly into a Clearvo e-invoice submission — no mapping required. See E-Invoicing Integration.
Input Formats
Clearvo normalises incoming field values server-side before validation runs. You can send data in the format your billing system or platform already produces — no pre-processing or mapping layer needed on your end.
Countries
The country field on any address accepts ISO 3166-1 alpha-2, alpha-3, or an English country name. All forms are resolved to alpha-2 internally.
| What you send | Resolved to | Notes |
|---|---|---|
"DE" | "DE" | Already canonical — passed through unchanged |
"DEU" | "DE" | ISO 3166-1 alpha-3 |
"GBR" | "GB" | As returned by Apple StoreKit storefront |
"USA" | "US" | |
"Germany" | "DE" | English country name |
"United States" | "US" | |
"Netherlands" / "Holland" | "NL" | Common informal names are also recognised |
"Czechia" / "Czech Republic" | "CZ" |
Currencies
The currency and reportingCurrency fields accept ISO 4217 codes or English currency names.
| What you send | Resolved to |
|---|---|
"EUR" / "eur" | "EUR" |
"Euro" / "euros" | "EUR" |
"US Dollar" / "Dollar" | "USD" |
"British Pound" / "Pound Sterling" | "GBP" |
"Japanese Yen" | "JPY" |
"Canadian Dollar" | "CAD" |
Regions (US states and Canadian provinces)
The region field on US and Canadian addresses accepts full state or province names as well as the standard 2-letter codes.
| What you send | Resolved to | Notes |
|---|---|---|
"California" | "CA" | As returned by Google Play buyerState |
"New York" | "NY" | |
"District of Columbia" / "Washington DC" | "DC" | |
"British Columbia" | "BC" | |
"Ontario" | "ON" | |
"Québec" / "Quebec" | "QC" | Accented forms accepted |
B2B override
By default, Clearvo infers B2B treatment from a valid customer.taxId — no additional flag is needed for standard B2B checkouts. For cases where you know the buyer is a business but don't have their VAT number at checkout time, set customer.b2bOverride: true.
| Scenario | What to send |
|---|---|
| B2C consumer sale | Omit taxId and b2bOverride — B2C treatment is the default |
| B2B sale, VAT number known | Supply customer.taxId — Clearvo verifies it and applies reverse charge or zero-rating automatically |
| B2B sale, VAT number not available | Set customer.b2bOverride: true — skips VAT number validation and applies full B2B treatment for the buyer's location |
Platform integration examples
These patterns work without any field transformation on your side.
// Google Play Orders API returns buyerCountry (alpha-2) + buyerState (full name)
{
"currency": "USD",
"customer": {
"type": "consumer",
"billingAddress": {
"country": "US",
"region": "California"
}
},
"lineItems": [{ "id": "1", "amount": 9.99, "productName": "App subscription" }]
}
// "consumer" → B2C, "California" → "CA" — both resolved before validation
// Apple StoreKit 2 returns storefront as ISO 3166-1 alpha-3
{
"currency": "USD",
"customer": {
"billingAddress": { "country": "GBR" }
},
"lineItems": [{ "id": "1", "amount": 4.99, "productName": "In-app purchase" }]
}
// "GBR" → "GB" — UK VAT (20%) applied
Calculate
Calculates tax for a single transaction. By default the result is ephemeral — not recorded in your account. Set "commit": true to persist it to your audit trail and expose it in the dashboard.
Endpoint
POST https://api.clearvo.io/v1/tax/calculate
Request body
curl https://api.clearvo.io/v1/tax/calculate \
-X POST \
-H "x-api-key: csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"commit": true,
"idempotencyKey": "order-abc-123",
"seller": {
"country": "IE",
"taxId": "IE1234567T"
},
"customer": {
"taxId": "DE987654321",
"address": {
"country": "DE",
"postalCode": "10115"
}
},
"lineItems": [
{
"id": "line-1",
"amount": 500.00,
"quantity": 5,
"productName": "Enterprise software licence",
"taxCategory": "digital-services"
},
{
"id": "line-2",
"amount": 120.00,
"quantity": 2,
"productName": "Printed user manual"
}
],
"vatUnverifiableFallback": "conservative"
}'
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
| currency | string | Yes | ISO 4217 currency code for all amounts in the request (e.g. "EUR", "USD", "GBP"). |
| commit | boolean | No | Default: false. When true, the calculation is persisted to your account's audit trail and appears in the dashboard. Committed calculations are billed. Ephemeral calculations are not recorded and are only intended for quoting. |
| idempotencyKey | string | No | A unique key you provide (e.g. your order ID). If a committed calculation already exists for this key under your account, the original result is returned and no new calculation is performed. Ignored for ephemeral calculations (commit: false). |
| seller.country | string | Yes | ISO 3166-1 alpha-2 country code of the seller's registered address. |
| seller.taxId | string | No | Seller's VAT number or tax ID. Used to determine whether reverse charge or IOSS rules apply. |
| seller.iossNumber | string | No | Seller's IOSS (Import One-Stop Shop) registration number. When present, IOSS treatment is applied to qualifying B2C cross-border goods shipments (≤€150) instead of standard import VAT. |
| customer.b2bOverride | boolean | No | When true, applies B2B treatment immediately without requiring a VAT number. Use when the buyer is a known business but their VAT ID is not available at the time of the request. Omit for all normal B2C and B2B-with-taxId flows. |
| customer.taxId | string | No | Customer's VAT number. When supplied, Clearvo verifies it against VIES (with a 300ms timeout) and applies B2B treatment — reverse charge, intra-community zero-rating, or export zero-rating — based on the seller and buyer locations. |
| customer.address.country | string | Yes | ISO 3166-1 alpha-2 country code of the customer's address. This is the primary jurisdiction signal for physical goods and B2B transactions. |
| customer.address.region | string | Yes (US) | State code (e.g. "CA" for California, "TX" for Texas). Required for US (country: "US") — no country-level US rate exists. For Canada, optional; supply the province code (e.g. "ON", "AB") for accurate provincial rate lookup; country-level GST (5%) is applied when region is omitted. |
| customer.address.postalCode | string | No | Postal code. Used for special territory detection — for example, Canary Islands postal codes (35xxx–38xxx) are treated as outside the EU VAT area despite using an ES country code. |
| customer.ipAddress | string | No | Customer's IP address. Used as a secondary jurisdiction signal for B2C digital services under the EU 2-of-3 rule. |
| customer.binCountry | string | No | ISO country code derived from the customer's card BIN. Used as a third jurisdiction signal for B2C digital services under the EU 2-of-3 rule. |
| lineItems[].id | string | Yes | Your identifier for this line item. Echoed back in the response — use it to match results to your order lines. |
| lineItems[].amount | number | Yes | Line item amount in the transaction currency. Whether this is tax-exclusive or tax-inclusive depends on the seller's pricingModel configured on their entity. |
| lineItems[].quantity | number | No | Default: 1. The quantity of units. The amount field should represent the total line amount (not the per-unit price), so quantity is informational only and does not affect the tax calculation. |
| lineItems[].productName | string | Yes | Plain-language product name or description. Clearvo uses this to classify the product and select the correct VAT rate band (e.g. digital services vs printed books). No HS codes required. |
| lineItems[].taxCategory | string | No | Explicit product category slug to override AI classification (e.g. "digital-services", "books", "food"). Use when you know the correct category and want deterministic results. See Product Classification for available slugs. |
| vatUnverifiableFallback | "conservative" | "permissive" | No | Default: "conservative". Controls behaviour when VIES cannot verify a customer's VAT ID within the timeout. "conservative" treats the customer as B2C (applies standard local rate). "permissive" applies B2B reverse charge rules anyway and marks the result as degraded: true. |
| vatValidation | "full" | "format" | "none" | No | Default: "full". Controls how a B2B customer's VAT ID is validated. "full" — live VIES lookup with a 300ms timeout (default; most accurate, adds ~100–300ms latency). "format" — format check only, no network call; a structurally valid ID is trusted without a degraded flag. "none" — no validation at all; the VAT ID is trusted entirely. Use "format" or "none" when VIES latency is a concern or you are processing at high volume. Can also be set at account level via Account Settings. |
| transactionType | "invoice" | "credit_note" | No | Default: "invoice". Set to "credit_note" when issuing a credit note against a prior invoice (e.g. from SAP or NetSuite). The engine negates all line item amounts before calculation — the same jurisdiction, treatment, and rate logic applies, but all resulting amounts are negative. See Credit Notes & Refunds. |
| relatedCalculationId | string | Conditionally required | The calculationId of the original invoice being reversed. Required when transactionType is "credit_note" and commit is true. The referenced calculation must belong to the same entity. Maximum 50 characters. |
Successful response
See the Quickstart for a full response example. For the complete field reference, see Response Reference.
Error responses
422 Unprocessable Entity for request bodies that are valid JSON but fail field-level validation (missing fields, wrong types, constraint violations). The error field in the response body identifies the failing field and reason — e.g. "customer: Required" or "merchantRef: String must contain at most 100 character(s)". Fix the field named in error to resolve it. 400 Bad Request is reserved for scope-level errors (e.g. using an account-scoped key on an entity-only endpoint).| HTTP | When it occurs | How to fix |
|---|---|---|
| 422 | Missing required field (customer, lineItems, currency, or a line item id / amount) | Check the error field — it names the failing path (e.g. "customer: Required"). Add the missing field. |
| 422 | currency is not exactly 3 characters, or lineItems is an empty array | Supply a valid ISO 4217 currency code (e.g. "EUR") and at least one line item. |
| 422 | merchantRef exceeds 100 characters | Truncate your reference to 100 characters or fewer. |
| 422 | customer.billingAddress.region (or shippingAddress.region) is missing when country is "US" | Supply the state code (e.g. "CA", "TX"). No country-level US tax rate exists — state is mandatory for all US addresses. |
| 400 | An account-scoped key was used without an X-Entity-Id header | Add X-Entity-Id: <entityId> to identify the target entity, or use an entity-scoped key. |
| 403 | The entity in X-Entity-Id does not belong to this account | Check that the entity ID belongs to the account associated with this API key. |
| 401 | The x-api-key header is missing or the key is unrecognised | Include a valid x-api-key header on every request. |
| 409 | A committed calculation already exists for this idempotencyKey with a different request body | Each idempotencyKey is permanently bound to the first request body that used it. Use a fresh key for a different transaction. |
| 422 | transactionType is "credit_note", commit is true, but relatedCalculationId is absent | Supply the calculationId of the original invoice in relatedCalculationId. |
| 422 | relatedCalculationId is provided but the referenced calculation does not exist or belongs to a different entity | Verify the ID — it must be a committed calculation for this entity. Ephemeral (uncommitted) calculations are not valid targets. |
Credit Notes & Refunds
Clearvo supports two distinct patterns for reversing a committed transaction, chosen based on how your billing or ERP system models reversals.
Credit notes (B2B / ERP path)
ERP systems such as SAP, NetSuite, and Oracle issue a formal credit note document when a sale is reversed. Submit this as a new calculation with transactionType: "credit_note". Clearvo negates all line item amounts before the calculation pipeline runs — the same jurisdiction, treatment, and rate logic applies unchanged, but every amount comes out negative. This keeps the credit note fully audit-traceable as its own document with its own calculationId.
curl https://api.clearvo.io/v1/tax/calculate \
-X POST \
-H "x-api-key: csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"commit": true,
"transactionType": "credit_note",
"relatedCalculationId": "cl_calc_01j4...",
"seller": { "country": "IE", "taxId": "IE1234567T" },
"customer": {
"taxId": "DE987654321",
"address": { "country": "DE" }
},
"lineItems": [
{
"id": "line-1",
"amount": 500.00,
"productName": "Enterprise software licence"
}
]
}'
The response is identical in shape to a regular calculation — all amounts are negative (e.g. taxableAmount: -500.00, taxAmount: -95.00). The response includes transactionType: "credit_note" and relatedCalculationId.
relatedCalculationId is required when committing a credit note. It must reference a previously committed invoice calculation belonging to the same entity. Ephemeral (uncommitted) calculations cannot be referenced. When commit: false, the field is optional — useful for quoting the credit note amount before committing it.Refunds (B2C / billing system path)
B2C billing systems such as Stripe, Chargebee, and Recurly process a payment refund without issuing a separate credit note document. Use the refund endpoint to mark the original calculation as refunded.
Endpoint
POST https://api.clearvo.io/v1/tax/calculate/{id}/refund
curl https://api.clearvo.io/v1/tax/calculate/cl_calc_01j4.../refund \
-X POST \
-H "x-api-key: csk_live_..."
{
"ok": true,
"calculationId": "cl_calc_01j4...",
"refundedAt": "2026-06-24T10:30:00.000Z"
}
Error responses
| HTTP | When it occurs | How to fix |
|---|---|---|
| 404 | The calculation does not exist or belongs to a different entity | Check that the ID is correct and the API key is for the entity that owns this calculation. |
| 409 | The calculation has already been marked as refunded | No action needed — the refund was already processed. The response body includes the original refundedAt timestamp. |
| 422 | The calculation is a sandbox calculation | Sandbox calculations cannot be marked as refunded — they are not billed and do not affect compliance thresholds. |
| 422 | The calculation is a credit note (not an invoice) | Credit notes cannot be refunded. If the credit note was issued in error, issue a correcting invoice instead. |
Compliance threshold impact
Both patterns exclude the reversed amount from your compliance obligation totals immediately:
- Credit notes — the negative
total_amountreduces the threshold SUM automatically, because the SUM aggregates all committed amounts for the entity and country. - Refunds — the refunded calculation is excluded from the threshold SUM as soon as
refunded_atis set. The Compliance Radar reflects this in real time.
Batch Calculation
Calculate tax for up to 50 transactions in one request. Each item in the batch is processed independently and in parallel — an error on one item does not affect the others. Per-item errors are returned inline at the item's index.
Endpoint
POST https://api.clearvo.io/v1/tax/calculate/batch
curl https://api.clearvo.io/v1/tax/calculate/batch \
-X POST \
-H "x-api-key: csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"currency": "EUR",
"seller": { "country": "IE", "taxId": "IE1234567T" },
"customer": { "address": { "country": "DE" } },
"lineItems": [{ "id": "l1", "amount": 100.00, "productName": "SaaS subscription" }]
},
{
"currency": "GBP",
"seller": { "country": "GB", "taxId": "GB123456789" },
"customer": { "address": { "country": "GB" } },
"lineItems": [{ "id": "l1", "amount": 49.99, "productName": "Printed guidebook" }]
}
]
}'
{
"results": [
{
"index": 0,
"calculationId": "cl_calc_01j4...",
"totals": { "subtotal": 100.00, "totalTax": 19.00, "total": 119.00 },
/* … full TaxCalculateResponse … */
},
{
"index": 1,
"error": "invalid_country: 'ZZ' is not a valid ISO country code"
}
],
"processedAt": "2026-06-21T12:00:00.000Z"
}
Each result includes an index matching its position in the items array. Successful items contain the full TaxCalculateResponse shape. Items that failed contain only index and error. The top-level HTTP status is always 200 if the batch request itself was valid — check each item's error field individually.
taxCategory slugs on line items eliminates this bottleneck.Exemptions
Store tax exemption records for specific customers. Use these endpoints to log that a customer holds a valid exemption certificate for a jurisdiction and product category. To apply an exemption in a calculation, pass "taxCode": "E" on the relevant line items — the engine processes it as exempt. Automated certificate matching during calculation is on the roadmap.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/tax/exemptions | List all exemptions. Filter by country, customerTaxId, or taxCategorySlug. |
| POST | /v1/tax/exemptions | Create a new exemption. |
| GET | /v1/tax/exemptions/{id} | Retrieve a single exemption. |
| DELETE | /v1/tax/exemptions/{id} | Delete an exemption (e.g. when a certificate expires). |
curl https://api.clearvo.io/v1/tax/exemptions \
-X POST \
-H "x-api-key: csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"country": "US",
"region": "TX",
"customerTaxId": "12-3456789",
"taxCategorySlug": "software",
"reason": "Non-profit exemption certificate",
"certificateRef": "TX-NPO-2026-00123",
"validFrom": "2026-01-01",
"validTo": "2026-12-31"
}'
Exemption fields
| Field | Type | Required | Description |
|---|---|---|---|
| country | string | Yes | ISO country code the exemption applies to. |
| region | string | No | State or province code. Leave null for a country-wide exemption. |
| customerTaxId | string | Yes | The customer's tax ID that this exemption is issued to. |
| taxCategorySlug | string | No | Limit the exemption to a specific product category. If null, the exemption applies to all categories in the jurisdiction. |
| reason | string | No | Human-readable description for your records (e.g. "Non-profit exemption certificate"). |
| certificateRef | string | No | Your reference number for the exemption certificate. Stored for audit purposes. |
| validFrom | string | Yes | YYYY-MM-DD. Date from which the exemption is valid. |
| validTo | string | No | YYYY-MM-DD. Expiry date. If omitted, the exemption does not expire. |
Tax Obligations
Track where your entity has a tax registration obligation — based on economic nexus thresholds, physical presence, or a manual override. The calculation engine uses obligation records to determine whether to apply seller-not-registered treatment.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/tax/obligations | List all obligations. Filter by country, registrationStatus, or obligationStatus. |
| GET | /v1/tax/obligations/{id} | Retrieve a single obligation. |
| PATCH | /v1/tax/obligations/{id} | Update registration status, registration number, or obligation status. |
curl https://api.clearvo.io/v1/tax/obligations/oblig_01j4... \
-X PATCH \
-H "x-api-key: csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"registrationStatus": "REGISTERED",
"registrationNumber": "DE987654321",
"obligationStatus": "COMPLIANT"
}'
Obligation fields
| Field | Type | Description |
|---|---|---|
| country | string | ISO country code this obligation applies to. |
| region | string | null | State or province. Null for country-level obligations. |
| registrationStatus | "REGISTERED" | "PENDING" | "NOT_REGISTERED" | Current registration status with the tax authority in this jurisdiction. Controls whether the engine applies local tax rates or seller-not-registered treatment. |
| registrationNumber | string | null | The tax registration number issued by the authority. Set when registrationStatus is REGISTERED. |
| obligationStatus | "COMPLIANT" | "MONITORING" | "ACTION_REQUIRED" | Overall compliance posture. ACTION_REQUIRED surfaces in the dashboard as a priority item. |
| thresholdAmount | number | null | The economic nexus threshold for this jurisdiction (in local currency). Informational only — Clearvo does not automatically track sales against this threshold. |
| currentPeriodAmount | number | null | Your current period sales amount in this jurisdiction. Update this via PATCH to keep the obligation status current. |
| estimatedExposure | number | null | Estimated tax exposure if registered (computed field — not updateable directly). |
Product Catalogue
Every product that passes through /v1/tax/calculate is classified by AI and cached per account. The Product Catalogue endpoints let you pre-classify your full product catalogue, inspect AI-assigned slugs, approve or correct them, and bulk-load classifications from an ERP export — all before go-live, so the first real transaction is never slowed by an AI round-trip.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/tax/products | List classified products. Filter by status (APPROVED | NEEDS_REVIEW), entityId (account-scoped keys only), page, limit (max 100). |
| POST | /v1/tax/products | Classify a single product. Providing taxCategory bypasses AI and saves immediately as APPROVED. |
| PATCH | /v1/tax/products/{id} | Approve or correct a classification. Accepts slug and status (APPROVED | NEEDS_REVIEW | MANUAL). |
| DELETE | /v1/tax/products/{id} | Soft-delete a product. Sets deleted_at and hides it from all reads — classification history is preserved. Use the restore endpoint to reactivate. |
| POST | /v1/tax/products/{id}/restore | Reactivate a soft-deleted product. Body: { "reason": "string" } (required — logged to audit trail). |
| POST | /v1/tax/products/bulk | Classify up to 500 products in one call. Accepts text/csv or application/json. |
curl https://api.clearvo.io/v1/tax/products \
-X POST \
-H "x-api-key: csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"productCode": "PLAN-BIZ-ANNUAL",
"productName": "Business plan — annual subscription",
"productDescription": "Cloud software subscription, B2B"
}'
curl https://api.clearvo.io/v1/tax/products/bulk \
-X POST \
-H "x-api-key: csk_live_..." \
-H "Content-Type: text/csv" \
--data-binary 'product_code,product_name,product_description,tax_category
PLAN-BIZ,Business plan,,saas_business
EBOOK-01,EU Tax Guide 2026,Comprehensive EU VAT reference,
CONSULT-HR,HR transformation workshop,Half-day onsite session,'
Classification fields
| Field | Type | Description |
|---|---|---|
| productCode | string | Your SKU or ERP code. Used as the cache key — the same product is classified at most once per account. |
| productName | string | Plain-language name fed to the AI classifier. Required unless productCode is supplied. |
| productDescription | string | Additional context for the classifier. Include when the product name alone is ambiguous. |
| taxCategory | string | Explicit slug override (e.g. "saas_business", "ebooks"). Bypasses AI — confidence is set to 1.0 and status is APPROVED immediately. |
| status | APPROVED | NEEDS_REVIEW | AI confidence ≥ 0.85 → APPROVED automatically. Below 0.85 → NEEDS_REVIEW until approved via PATCH or the dashboard. |
Tax Categories
The Tax Categories endpoint returns the complete, live taxonomy of product category slugs — the same set the engine uses to classify line items. Call this endpoint to discover valid values for taxCategory on calculate requests and defaultTaxCategorySlug in account settings.
Endpoint
GET https://api.clearvo.io/v1/tax/categories
Accepts both entity-scoped and account-scoped API keys.
curl https://api.clearvo.io/v1/tax/categories \
-H "x-api-key: csk_live_..."
{
"object": "list",
"count": 48,
"categories": [
{
"slug": "api_data_services",
"name": "API Access / Data Feeds / Integrations",
"defaultTaxCode": "S",
"eligibleSchemes": ["STANDARD", "OSS_UNION", "OSS_NON_UNION", "VOEC"]
},
{
"slug": "ebooks",
"name": "eBooks / Digital Publications",
"defaultTaxCode": "AA",
"eligibleSchemes": ["STANDARD", "OSS_UNION", "OSS_NON_UNION", "VOEC"]
},
{
"slug": "saas_business",
"name": "SaaS / Cloud Software (Business Use)",
"defaultTaxCode": "S",
"eligibleSchemes": ["STANDARD", "OSS_UNION", "OSS_NON_UNION", "VOEC"]
},
"..."
]
}
Response fields
| Field | Type | Description |
|---|---|---|
| slug | string | The category identifier to use in taxCategory on line items, or as defaultTaxCategorySlug in account settings. |
| name | string | Human-readable description of the category. |
| defaultTaxCode | string | The EN16931 tax code most commonly applied to this category (S = standard, AA = reduced, E = exempt). The actual code used in a calculation may differ depending on country-specific rules. |
| eligibleSchemes | string[] | OSS/IOSS schemes this category qualifies for: STANDARD, OSS_UNION, OSS_NON_UNION, IOSS, VOEC. Physical goods include IOSS; location-specific services include STANDARD only. |
Registrations
Registrations tell the calculation engine where your entity is authorised to collect tax. A jurisdiction with no registration record defaults to REGISTERED (tax collected). Set a jurisdiction to NOT_REGISTERED and the engine returns tax code O (outside scope, 0%) for all transactions there. Adding an IOSS number enables IOSS treatment for qualifying EU B2C shipments ≤ €150.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/tax/registrations | List registrations for the entity. Account-scoped keys must supply ?entityId=. Returns collectionStatus, collectFromDate, and canCollectTax on each registration when Tax Calculations is enabled. |
| POST | /v1/tax/registrations | Create a registration. Account-scoped keys must include X-Entity-Id. |
| PATCH | /v1/tax/registrations/{id} | Set the collection start date for a registration. Pass collectFromDate: null to collect immediately, or an ISO date string to defer. Returns the updated collectionStatus. |
| DELETE | /v1/tax/registrations/{id} | Delete a tax number registration. Account-scoped keys must include X-Entity-Id. |
Collection status
When Tax Calculations is enabled for your account, each registration carries a collectionStatus that controls whether Clearvo applies tax for that jurisdiction.
| Status | Meaning |
|---|---|
COLLECTING | Tax is being collected — collectFromDate is set to today or a past date. |
DEFERRED | Collection starts on a future collectFromDate. No tax collected until that date. |
SETUP_NEEDED | Tax Calculations is enabled but no collectFromDate has been set. Set one via PATCH /v1/tax/registrations/{id}. |
null | Tax Calculations is not enabled for this account. |
The list response also includes two top-level flags: taxCalcEnabled (boolean — whether the product is active) and taxCalcSetupRequired (boolean — true when any registration is in SETUP_NEEDED state).
curl https://api.clearvo.io/v1/tax/registrations \
-X POST \
-H "x-api-key: csk_live_ent_..." \
-H "Content-Type: application/json" \
-d '{
"type": "VAT",
"country": "DE",
"taxNumber": "DE123456789"
}'
# Start collecting immediately
curl https://api.clearvo.io/v1/tax/registrations/<id> \
-X PATCH \
-H "x-api-key: csk_live_ent_..." \
-H "Content-Type: application/json" \
-d '{ "collectFromDate": null }'
# Defer to a future date
curl https://api.clearvo.io/v1/tax/registrations/<id> \
-X PATCH \
-H "x-api-key: csk_live_ent_..." \
-H "Content-Type: application/json" \
-d '{ "collectFromDate": "2026-07-01" }'
Registration types
| Type | Description | Engine effect |
|---|---|---|
| VAT | Standard national VAT registration | Sets obligation to REGISTERED; tax collected at local rate |
| IOSS | EU Import One-Stop Shop number | Writes iossNumber to the entity master; enables IOSS treatment for eligible B2C goods ≤ €150 shipped into the EU |
| UNION_OSS | EU One-Stop Shop (Union scheme) | Sets obligation to REGISTERED with OSS scheme flag |
| NON_UNION_OSS | EU One-Stop Shop (Non-Union scheme) | Sets obligation to REGISTERED with OSS scheme flag |
Account Settings
Account-level defaults that apply to every calculation made with your API key. All three settings can be overridden per-request — the cascade is: request field → product cache → account setting → hardcoded default.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/tax/settings | Read current account settings. Returns defaults if no settings row has been written yet. |
| PATCH | /v1/tax/settings | Update one or more settings. Partial updates — omitted fields are unchanged. |
curl https://api.clearvo.io/v1/tax/settings \
-X PATCH \
-H "x-api-key: csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"vatValidationMode": "format",
"vatUnverifiableTreatment": "consumer",
"defaultPriceIncludesTax": false,
"defaultTaxCategorySlug": "saas_business"
}'
Settings fields
| Field | Type | Default | Description |
|---|---|---|---|
| vatValidationMode | "full" | "format" | "none" | "full" | Account-level default for VAT ID validation. Overridden per-request by the vatValidation request field. "full" — live VIES lookup (default). "format" — format check only, no VIES call. "none" — no validation; all VAT IDs trusted. |
| vatUnverifiableTreatment | "consumer" | "business" | "consumer" | How to treat B2B transactions when the customer's VAT ID cannot be verified (only applies when vatValidationMode is "full"). "consumer" charges tax at B2C rates (conservative). "business" applies reverse charge and sets degraded: true. |
| defaultPriceIncludesTax | boolean | false | Whether line item amounts are tax-inclusive by default. Can be overridden per line item via amountIncludesTax. |
| defaultTaxCategorySlug | string | null | null | Fallback product category slug when AI classification fails. If null, the engine applies the standard rate. |
| availableTaxCategories | array | — | Read-only. Full list of active category slugs and names available in your account (GET only). |
Response Reference
Full field reference for the TaxCalculateResponse object returned by both the single and batch calculation endpoints.
Top-level fields
| Field | Type | Description |
|---|---|---|
| calculationId | string | Unique ID for this calculation (cl_calc_* prefix). Present on both ephemeral and committed calculations. |
| entityId | string | The entity (fiscal identity) that processed this calculation. Confirms which entity-scoped API key was used — useful for Merchant of Record setups where multiple entities share an account. |
| committed | boolean | Whether this calculation was persisted to your audit trail. |
| sandbox | boolean | True when the request was made with a csk_test_* key. |
| degraded | boolean | True when the engine encountered a partial failure (e.g. rate service timeout, VIES unreachable). The calculation still succeeds but uses a fallback rate of 0%. |
| degradedReason | string | null | Human-readable explanation of why the result is degraded. Only present when degraded: true. |
| currency | string | ISO 4217 currency code, echoed from the request. |
| transactionType | string | "invoice" or "credit_note" — echoed from the request (defaults to "invoice"). Credit note responses have negative amounts on all line items and totals. |
| relatedCalculationId | string | null | The calculationId of the original invoice this credit note reverses. Only present when transactionType is "credit_note" and the field was provided in the request. |
| refundedAt | string | null | ISO 8601 timestamp. Set when POST /v1/tax/calculate/{id}/refund has been called. null for non-refunded calculations. |
jurisdiction
| Field | Type | Description |
|---|---|---|
| jurisdiction.country | string | ISO country code of the resolved tax jurisdiction. |
| jurisdiction.region | string | null | State or province of the resolved jurisdiction. Non-null for US/Canadian transactions where sub-national rates apply. |
| jurisdiction.method | string | How the jurisdiction was determined: SHIPPING_ADDRESS, BILLING_ADDRESS, IP_GEOLOCATION, BIN_COUNTRY, or VAT_ID. |
| jurisdiction.precision | string | Confidence in the resolved jurisdiction: EXACT, POSTAL, REGION, or COUNTRY. |
| jurisdiction.addressPrecision | string | undefined | US transactions only. The address resolution tier used to look up the CCH rate: ROOFTOP (street address resolved to a zip+4 code), ZIP (postal code only), or STATE (region only). Not present for non-US transactions. |
| jurisdiction.conflicts | array | Non-empty when multiple jurisdiction signals disagree (e.g. billing address says DE, IP geolocation says FR). Each entry describes the conflicting signal and its country. The winning signal (used for tax) is indicated by method. |
rate, standardRate, reducedRate, and slugRate field across all Tax Calculation endpoints uses a decimal fraction: 0.19 means 19%, 0.07 means 7%, 0 means zero-rated. Never multiply or divide rates received from the API — use them directly in tax calculations (taxAmount = taxableAmount × rate).treatment
| Field | Type | Description |
|---|---|---|
| treatment.taxTreatment | string | The tax treatment applied. See Tax Treatments for all possible values and their meanings. |
| treatment.taxCode | string | EN16931 tax code: S, AA, AE, E, K, G, Z, or O. Use this directly on invoice line items when submitting via the e-invoicing API. |
| treatment.rate | number | Effective tax rate as a decimal fraction (0.19 = 19%, 0.07 = 7%). Zero for exempt, reverse-charge, and out-of-scope treatments. |
| treatment.rateBand | string | Rate band selected: STANDARD, REDUCED, SECOND_REDUCED, SUPER_REDUCED, ZERO, or EXEMPT. |
lineItems[]
| Field | Type | Description |
|---|---|---|
| id | string | Your line item ID, echoed from the request. |
| taxCode | string | EN16931 tax code for this line item. |
| rate | number | Effective tax rate for this line item as a decimal fraction (0.19 = 19%, 0 = zero-rated). |
| taxableAmount | number | Amount on which tax is calculated (always tax-exclusive, regardless of the seller's pricing model). |
| taxAmount | number | Tax amount for this line item (taxableAmount × rate). |
| totalAmount | number | Total including tax (taxableAmount + taxAmount). |
| rateBand | string | Rate band applied to this specific line item (may differ across lines if products fall into different bands). |
| classification.slug | string | Product category slug resolved for this line item. |
| classification.confidence | number | AI classification confidence score (0–1). 1.0 when an explicit taxCategory was provided. |
| classification.status | string | APPROVED — used with confidence; NEEDS_REVIEW — low confidence, recommend manual override; MANUAL — explicit taxCategory supplied; PENDING — classification failed, fallback used. |
| classification.fromCache | boolean | True if the classification came from a prior result cached for this product name. |
totals
| Field | Type | Description |
|---|---|---|
| totals.subtotal | number | Sum of all taxableAmount values across line items. |
| totals.totalTax | number | Sum of all taxAmount values across line items. |
| totals.total | number | Sum of all totalAmount values across line items (subtotal + totalTax). |
ioss (when applicable)
| Field | Type | Description |
|---|---|---|
| ioss.number | string | The IOSS registration number used for this transaction. |
| ioss.registrationCountry | string | The EU member state where the IOSS number is registered. |
| ioss.totalGoodsValue | number | Nominal total value of goods in the transaction currency (used to determine the ≤€150 threshold eligibility). |
| ioss.currency | string | Currency of the totalGoodsValue. |
rateSource
| Field | Type | Description |
|---|---|---|
| rateSource.provider | string | "CLEARVO" for all non-US transactions. "CCH" for US transactions, where rate data comes from the CCH (Wolters Kluwer) sales tax database. |
| rateSource.asOf | string | YYYY-MM-DD date of the rate data used. |
| rateSource.cacheHit | boolean | True if the rate was served from Clearvo's in-memory rate cache. False if a live lookup was performed. |
Tax Treatments
Clearvo selects a tax treatment for each transaction using a priority-ordered decision tree. The treatment determines the EN16931 tax code and effective rate applied to line items.
| Treatment | taxCode | Rate | When applied |
|---|---|---|---|
| STANDARD | S |
Country standard or reduced rate | Default B2C treatment. The buyer is in the seller's country, or the seller is registered in the buyer's country and no other rule takes priority. Correct rate band (STANDARD, REDUCED, etc.) is selected per product category. |
| IOSS | S |
Destination country rate | Non-EU seller with an IOSS number, selling physical goods to an EU B2C customer, where the total goods value is ≤€150. VAT is collected at the destination country's rate and declared via IOSS. |
| REVERSE_CHARGE | AE (non-EU→EU B2B) or K (EU→EU B2B cross-border) |
0% | B2B transaction where the buyer has a verified VAT ID and the transaction crosses an EU border. Tax liability shifts to the buyer. No VAT is charged on the invoice. |
| ZERO_RATED | Z |
0% | Product falls into a zero-rated category in the destination jurisdiction (e.g. children's clothing in the UK, basic foodstuffs in many EU countries). |
| EXEMPT | E |
0% | An exemption certificate has been registered for this customer's tax ID in the relevant jurisdiction and product category. See Exemptions. |
| OUT_OF_SCOPE | O |
0% | The transaction falls outside the scope of indirect tax (e.g. financial services, insurance, inter-group transactions where both parties are in the same VAT group). |
| SELLER_NOT_REGISTERED | O |
0% | The seller's tax obligation record for the buyer's jurisdiction has registrationStatus: "NOT_REGISTERED". No tax is charged. The obligation status surfaces in the dashboard as MONITORING or ACTION_REQUIRED. |
| EXPORT | G |
0% | EU seller, non-EU buyer — goods or services exported outside the EU VAT area. Zero-rated as an export. |
taxCode field in the response maps directly to the taxCode field accepted by the Clearvo e-invoicing /v1/send endpoint. No translation or lookup is required — the calculation output can flow straight into your invoice submission.Jurisdiction Resolution
Clearvo determines which tax authority's rules apply to a transaction using a rule-based resolution chain. The resolved jurisdiction, resolution method, and any conflicting signals are all returned in the response.
For tangible goods, the shipping destination controls the jurisdiction. If a shippingAddress.country is provided, it takes priority over all other signals.
For B2B transactions, Clearvo extracts the country prefix from the customer's taxId (e.g. DE from DE987654321) and uses it as the primary signal. If the VAT ID prefix is absent or unverifiable, the billing address country is used.
EU regulations require sellers of B2C digital services to collect two non-contradictory pieces of evidence of the customer's location. Clearvo evaluates up to three signals: billing address country, IP geolocation country, and card BIN country. The majority wins. If all three agree, precision is EXACT; if only two agree, it is COUNTRY. Disagreements are listed in jurisdiction.conflicts.
Some territories use the country code of their parent state but are outside the EU VAT area. Clearvo detects these automatically from the postal code:
- Canary Islands (ES 35xxx–38xxx) — outside EU VAT
- Ceuta & Melilla (ES 51xxx, 52xxx) — outside EU VAT
- Åland Islands (FI 22xxx) — outside EU VAT
- Madeira (PT 90xxx–93xxx) — inside EU VAT
For US transactions, Clearvo looks up the applicable sales tax rate from the CCH database using one of three precision tiers, depending on which address fields are supplied and your account's usAddressPrecision setting:
- ROOFTOP — a street address (
line1) is present and your account setting isusAddressPrecision: "rooftop"(the default). Clearvo resolves the address to a zip+4 code and looks up the exact rate for that delivery-point range in the CCH database. "Rooftop" is the industry-standard term for address-level precision in US sales tax — the CCH rate table is keyed on zip+4 ranges, which cover approximately 10–20 delivery points. - ZIP — only a postal code is available, or your account setting is
usAddressPrecision: "zip"(which skips street-address resolution even whenline1is present). Clearvo uses the zip5 code directly against the CCH database. - STATE — only a state code (
region) is available with no postal code. Clearvo applies the state-level rate. Note that USregionis always required — if it is missing, the API returns a 422 error.
The tier used is returned in jurisdiction.addressPrecision on every US response. You can switch your account between ROOFTOP and ZIP precision under Settings → Tax Calculations.
When the resolution chain cannot determine the jurisdiction from available signals, Clearvo falls back to the seller's country and marks the result as degraded: true with a degradedReason of "jurisdiction_unresolved".
Product Classification
Clearvo classifies each line item into a product category to determine the correct VAT rate band — for example, whether a digital product is taxed at the standard rate or a reduced "cultural goods" rate. No HS codes are required.
Classification pipeline
taxCategory)
If you pass a taxCategory slug on a line item, that category is used with confidence 1.0 and status: "MANUAL". No AI call is made. Use this for predictable, stable product catalogues where you know the correct classification.
Clearvo checks whether a prior classification exists for this product (matched by product code or a hash of the product name). If a cached result is found with sufficient confidence, it is used immediately with fromCache: true. Cache entries are per-account and persist across sessions.
For unrecognised products, Clearvo passes the productName to an AI classifier, which maps it to the closest available category slug. The result is returned with a confidence score and cached for future calls. Products with low confidence scores return status: "NEEDS_REVIEW" — you can supply an explicit taxCategory on the next call to override.
Common category slugs
A representative sample — call GET /v1/tax/categories for the complete live list.
| Slug | Typical rate band | Examples |
|---|---|---|
saas_business | STANDARD | SaaS subscriptions (B2B use) |
saas_personal | STANDARD | SaaS subscriptions (consumer / personal use) |
api_data_services | STANDARD | API access, data feeds, webhooks |
streaming_video | STANDARD | Video streaming subscriptions |
ebooks | REDUCED (most EU countries) | Digital books, audiobooks, online newspapers |
books_physical | REDUCED (most EU countries) | Printed books, newspapers, maps |
online_courses | EXEMPT (many jurisdictions) | E-learning, training subscriptions |
food_basic | REDUCED or ZERO | Groceries, basic unprocessed foods |
food_restaurant | REDUCED or STANDARD | Prepared meals, catering |
medical_devices | EXEMPT or REDUCED | Medical equipment, hearing aids, spectacles |
pharmaceuticals | REDUCED or ZERO | Prescription drugs, licensed medications |
clothing_children | REDUCED or ZERO | Children's apparel (zero-rated in UK and IE) |
financial_services | EXEMPT | Banking fees, insurance premiums, credit services |
professional_services | STANDARD | Consulting, legal, accounting, advisory |
physical_goods_general | STANDARD | Catch-all for physical goods with no specific slug |
digital_general | STANDARD | Catch-all for digital goods with no specific slug |
nontaxable | ZERO / EXEMPT | Items not subject to indirect tax in any jurisdiction |
taxCategory slug for each product in your catalogue. This eliminates AI classification latency, gives you deterministic results, and avoids any risk of misclassification. Reserve the AI classification path for edge cases or exploratory integrations.E-Invoicing Integration
Tax Calculation output is designed to feed directly into Clearvo e-invoice submissions. The taxCode and rate fields on each calculated line item match the fields expected by the POST /v1/send endpoint — no mapping layer is needed.
Example: calculate then invoice
{
// POST /v1/tax/calculate response (line items)
"lineItems": [{
"id": "line-1",
"taxCode": "S",
"rate": 0.19,
"taxableAmount": 100.00,
"taxAmount": 19.00,
"totalAmount": 119.00
}]
}
{
"invoiceNumber": "INV-2026-001",
"issueDate": "2026-06-21",
"currency": "EUR",
"country": "DE",
// supplier and buyer fields …
"lineItems": [{
"description": "Annual SaaS subscription",
"quantity": 1,
"unitPrice": 100.00,
"taxCode": "S", // ← from calculation response
"taxRate": 0.19, // ← from calculation response
"taxAmount": 19.00 // ← from calculation response
}]
}
EN16931 tax code reference
| Code | Name | When used |
|---|---|---|
S | Standard rate | Standard or reduced VAT/GST applied to the transaction |
AA | Lower rate | A lower rate than the standard rate applies (e.g. reduced band in EU) |
AE | VAT Reverse Charge | Non-EU seller to EU B2B buyer — buyer accounts for VAT |
K | VAT exempt (intra-community) | EU-to-EU cross-border B2B — intra-community supply |
G | Free export item, tax not charged | EU seller, non-EU buyer — export outside the EU VAT area |
E | Exempt from tax | Transaction is exempt under local rules (financial services, health, education) |
Z | Zero-rated goods | Goods or services taxable at 0% (e.g. children's clothing in UK) |
O | Services outside scope of tax | Transaction falls outside the indirect tax scope, or seller is not registered in the jurisdiction |
Sandbox
Use a csk_test_* API key to run calculations in sandbox mode. Sandbox calculations are identical to production in every respect except that they are not billed and the sandbox field on the response is true.
What runs normally in sandbox
- Jurisdiction resolution (all signals, postal territory detection, 2-of-3 rule)
- Rate lookup (same rate tables as production)
- Product classification (AI classifier and cache)
- Tax treatment determination (all rules, including IOSS and reverse charge)
- DB write when
commit: true(written to sandbox DB, not production) - Idempotency key enforcement (scoped to sandbox DB)
What is skipped in sandbox
- VIES customer VAT ID verification — the customer's VAT ID is accepted at face value; no live VIES call is made
- Billing — sandbox calculations do not count toward usage
curl https://api.clearvo.io/v1/tax/calculate \
-X POST \
-H "x-api-key: csk_test_..." \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"seller": { "country": "IE", "taxId": "IE1234567T" },
"customer": { "address": { "country": "FR" } },
"lineItems": [{ "id": "l1", "amount": 99.00, "productName": "SaaS subscription" }]
}'
{
"calculationId": "cl_calc_test_...",
"committed": false,
"sandbox": true,
"degraded": false,
"currency": "EUR",
"jurisdiction": { "country": "FR", "method": "BILLING_ADDRESS" },
"treatment": { "taxTreatment": "STANDARD", "taxCode": "S", "rate": 0.20 },
"lineItems": [{
"id": "l1",
"taxCode": "S",
"rate": 0.20,
"taxableAmount": 99.00,
"taxAmount": 19.80,
"totalAmount": 118.80
}],
"totals": { "subtotal": 99.00, "totalTax": 19.80, "total": 118.80 }
}
taxCode output is identical to production — the only difference is that the sandbox flag is set and no charge is incurred.