Skip to main content

Calculate taxes on your backend

Calculating taxes shouldn't be hard and tedious. With Quaderno it's as easy as this:

curl https://ACCOUNT_NAME.sandbox-quadernoapp.com/api/tax_rates/calculate?to_country=US&to_postal_code=10128 \
-u YOUR_API_KEY:x

The parameters to_country and to_postal_code represent your customer's location. All other data is inferred from your account's default configuration (that's why configuring your account is so important!). You don't even need to specify any amount, as you may prefer to do the calculation on your own once you get the correct tax_rate.

Now, depending on your account's configuration you may get very different responses! In this case we a response that contains "name": "Sales tax" and "rate": 8.875, which corresponds to "city": "NEW YORK".

Here's the full response based on our Sandbox configuration:

{
"city": "NEW YORK",
"country": "US",
"county": "NEW YORK",
"currency": "EUR",
"name": "Sales tax",
"notes": null,
"notice": "Only state tax rates are returned in test mode. Local tax rates will be returned only in live mode except for the following test zip codes: 90049,10128,60611,33132.",
"product_type": "good",
"rate": 8.875,
"region": "NY",
"tax_behavior": "exclusive",
"tax_code": "standard",
"taxable_part": 100.0,
"import": true,
"subtotal": null,
"tax_amount": null,
"total_amount": null,
"status": "taxable"
}

The status response field

Check the status response param to understand what happens on tax calculations:

  • taxable: the transaction is taxable and the tax calculator will return a non-zero rate.
  • non_taxable: The transaction is non taxable. This could happen because the particular product is not taxable (ie. a digital product in California), or because the region for a particular territory within your registered jurisdiction is exempted (for example, you're registered in France selling to a customer from New Caledonia).
  • not_registered: You're trying to calculate taxes in a jurisdiction you're not registered in!
  • reverse_charge: The transaction is "reverse charge" (usually a B2B sale), meaning you're not responsible to collect taxes for it, so they won't be calculated.
info

On Quaderno Sandbox, only state tax rates are returned for all US addresses, except for the following ZIP codes: 90049, 10128, 60611, 33132.

Let's see an example response trying to calculate taxes in a jurisdiction where you're not registered for tax collection. Quaderno will return an not_registered status, along with null name and a 0.0 rate:

{
"country": "CA",
"currency": "EUR",
"name": null,
"notes": null,
"product_type": "service",
"rate": 0.0,
"region": null,
"tax_behavior": "exclusive",
"tax_code": "eservice",
"taxable_part": null,
"import": true,
"subtotal": null,
"tax_amount": null,
"total_amount": null,
"status":"not_registered"
}

Depending on your use case, you may need to craft a more explicit request, overriding some product and account-level settings.

Advanced use cases

Selling multiple product types

When selling different products, you probably want to avoid relying on the default settings and specify tax_code and product_typeon each request.

In Quaderno you can use the following tax codes: eservice, ebook, saas, consulting, standard, reduced and exempt.

These, along with product_type which can be good or service, define what you're selling.

Building up on our previous example:

curl https://uruk-3243.sandbox-quadernoapp.com/api/tax_rates/calculate?to_country=US&to_postal_code=10128&product_type=service&tax_code=ebook \
-u YOUR_API_KEY:x

For our Sandbox Belgian based account, registered in the EU's VAT OSS / IOSS jurisdiction, we'll get a response like:

{
"country": "FR",
"currency": "EUR",
"name": "TVA",
"notes": null,
"product_type": "service",
"rate": 5.5,
"region": null,
"tax_behavior": "exclusive",
"tax_code": "ebook",
"taxable_part": 100.0,
"import": true,
"subtotal": null,
"tax_amount": null,
"total_amount": null,
"status": "taxable"
}

Calculating taxable amounts

You can get your tax_rate and calculate whatever you need based on that, or we can do the math for you by specifying a concrete amount, like 9.95:

curl https://uruk-3243.sandbox-quadernoapp.com/api/tax_rates/calculate?to_country=US&to_postal_code=10128&product_type=service&tax_code=ebook&amount=9.95 \
-u YOUR_API_KEY:x

Which returns:

{
"country": "FR",
"currency": "EUR",
"name": "TVA",
"notes": null,
"product_type": "service",
"rate": 5.5,
"region": null,
"tax_behavior": "exclusive",
"tax_code": "ebook",
"taxable_part": 100.0,
"import": true,
"subtotal": 9.95,
"tax_amount": 0.55,
"total_amount": 10.5,
"status": "taxable"
}

Taxable base is the amount you must use to calculate the tax amount.

The taxable part is the percentage of the subtotal to be used for calculating the tax amount. It's usually 100% but there are a few exceptions. For example, in Texas the taxable base is 80% for SaaS products.

Selling to businesses

When including the tax_id parameter, Quaderno will automatically check its validity and apply the reverse_charge status when needed.

Quaderno can validate tax IDs from the EU, United Kingdom, Switzerland, Québec (Canada), Australia, and New Zealand.

This API call:

curl https://uruk-3243.sandbox-quadernoapp.com/api/tax_rates/calculate?to_country=DE&tax_id=DE123456789&amount=100 \
-u YOUR_API_KEY:x

Will return:

{
"country": "DE",
"currency": "EUR",
"name": null,
"notes": "Tax amount subject to reverse charge",
"product_type": "service",
"rate": 0.0,
"region": null,
"tax_behavior": "exclusive",
"tax_code": "eservice",
"taxable_part": null,
"import": false,
"subtotal": 100.0,
"tax_amount": 0.0,
"total_amount": 100.0,
"status": "reverse_charge"
}

Selling from different places

You can override the seller's account address by specifying your own from_country.

This makes sense when shipping physical products from a warehouse placed in a different jurisdiction than the one where your bussiness is based.

Jurisdictions with additional taxes

For jurisdictions that need to apply two tax rates (e.g. some Canadian provinces), the response will include the second tax rate on fields with the additional_ suffix, like so:

{
"additional_name": "QST",
"additional_rate": 9.975,
"additional_taxable_part": 100.0,
"country": "CA",
"currency": "CAD",
"name": "GST",
"notes": null,
"product_type": "service",
"rate": 5.0,
"region": "QC",
"tax_behavior": "exclusive",
"tax_code": "standard",
"taxable_part": 100.0,
"import": false,
"subtotal": 100.0,
"tax_amount": 5.0,
"additional_tax_amount": 9.98,
"total_amount": 114.98,
"status":"taxable"
}