New France e-invoicing mandate goes live September 2026 — our implementation is ready. See all mandates →
Integration Guide

Integrate Tax Calculations

This guide walks through wiring live tax calculation into your checkout or billing flow, once your account setup (registrations, product catalogue, tax settings) is in place. For the full field-by-field reference, see the Tax Calculations reference docs. This guide is the shorter, sequential version: what to call, in what order, and why.

The core idea: call the same endpoint twice per transaction — once as an ephemeral quote (commit: false) to show tax before payment, and once committed (commit: true) after payment succeeds. Only committed calculations count toward your compliance threshold tracking and audit trail.
Integration Guide

Prerequisites

At least one tax registration for a country you sell into

Without a registration in a jurisdiction, calculations for that jurisdiction return SELLER_NOT_REGISTERED with a 0% rate — correct, but not what you want live. Check GET /v1/tax/registrations, or if you're integrating an AI agent, call list_registrations / add_registration via MCP.

An API key

Entity-scoped keys resolve their entity automatically. Account-scoped keys must send an X-Entity-Id header on every request.

Product catalogue classification — optional

You don't need to pre-classify products. Send a plain-language productName on each line item and Clearvo's AI classifier resolves the tax category on the fly. Pre-classifying via create_product is worthwhile once volume is high enough that you want classification cached rather than re-run per call.

Integration Guide

Step 1 — Quote tax at checkout

Call POST /v1/tax/calculate with commit: false (or omit commit — it defaults to false) as soon as you know the customer's address and cart contents. This is a live calculation — real rates, real jurisdiction resolution — but nothing is recorded, so you can call it as many times as the cart changes (address edits, quantity changes, coupon codes) with no side effects and no cost.

curl
curl https://api.clearvo.io/v1/tax/calculate \
  -X POST \
  -H "x-api-key: csk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "commit": false,
    "currency": "EUR",
    "seller": { "country": "IE", "taxId": "IE1234567T" },
    "customer": { "address": { "country": "DE" } },
    "lineItems": [
      { "id": "line-1", "amount": 100.00, "productName": "Annual SaaS subscription" }
    ]
  }'

Show summary.totalTax and summary.totalAmountWithTax in your checkout UI. Re-run this call whenever an input that affects tax changes — it's cheap and side-effect-free by design.

Integration Guide

Step 2 — Commit at payment success

When payment actually succeeds, call the same endpoint again with the final cart contents and commit: true. This is what actually gets recorded for your audit trail and rolls up into compliance threshold tracking (economic nexus, OSS/IOSS registration triggers, etc.) — an ephemeral quote from Step 1 never counts toward any of that on its own.

Always send an idempotencyKey on committed calls. Payment webhooks retry; without an idempotency key, a retried webhook creates a second committed calculation for the same sale — double-counting it in your compliance totals.
JavaScript
// e.g. inside a Stripe payment_intent.succeeded webhook handler
const response = await fetch('https://api.clearvo.io/v1/tax/calculate', {
  method: 'POST',
  headers: { 'x-api-key': process.env.CLEARVO_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    commit: true,
    idempotencyKey: paymentIntent.id, // re-using the payment provider's own event/charge id is the simplest safe key
    currency: 'EUR',
    seller: { country: 'IE', taxId: 'IE1234567T' },
    customer: { address: { country: 'DE' } },
    lineItems: [{ id: 'line-1', amount: 100.00, productName: 'Annual SaaS subscription' }],
  }),
});

const result = await response.json();
// Store result.calculationId against your own order record — you'll need it for refunds.

Store the returned calculationId against your order. You'll need it in Step 3 if the order is ever refunded.

Integration Guide

Step 3 — Handle refunds

When a payment is refunded (e.g. a Stripe charge.refunded event) and you're not issuing a separate B2B credit note document, call POST /v1/tax/calculate/{calculationId}/refund using the calculationId you stored in Step 2. This marks the calculation refunded and excludes it from compliance threshold totals — you don't need to compute or send the refunded amount yourself.

curl
curl https://api.clearvo.io/v1/tax/calculate/cl_calc_01j4.../refund \
  -X POST \
  -H "x-api-key: csk_live_..."

If instead you're issuing a formal B2B credit note from an ERP, use transactionType: "credit_note" on a new POST /v1/tax/calculate call referencing relatedCalculationId — see the Credit Notes & Refunds reference for the distinction.

Integration Guide

Step 4 — Handle errors gracefully

Two response fields matter most once you're live:

FieldWhat it meansWhat to do
degraded: trueA rate lookup failed internally (e.g. a rate-authority timeout); Clearvo returned a result anyway rather than blocking checkout.Complete the sale — don't fail checkout on this alone — but log it. Repeated degraded responses for the same jurisdiction are worth investigating.
taxTreatment: "SELLER_NOT_REGISTERED"You have no active registration in the customer's jurisdiction, so 0% was applied.Expected below your registration threshold. If you're seeing this for a jurisdiction you believe you're registered in, check list_registrations — the registration may be missing a collection date.

Never treat a non-2xx response from this endpoint as safe to ignore and complete checkout with no tax applied — that creates real, uncollected tax liability. Retry once on a network-level failure; if it persists, fail the checkout rather than silently charging 0%.

Integration Guide

Step 5 — Test with the MCP tool first

If you're building this integration with Claude Code, Cursor, or another MCP-connected AI agent, use the calculate_tax tool to try real requests against your actual account (sandbox or live) before writing the application code. This surfaces jurisdiction resolution, classification, and rate results directly in the conversation, so the code you end up writing is based on a request/response shape you've already verified — not one guessed from documentation.

Use a csk_test_* key (or ask the agent to check get_setup_status first) so test calls during development don't mix with production data.
Integration Guide

What's next