API tutorial: online videogames store

API tutorial: online videogames store

In this tutorial, we're gonna review all steps needed to integrate via Quaderno API, from registering our Tax ID on Tax Jurisdictions, to perform tax calculations and save the transactions to have full reports to help on filing taxes and to get alerts when need jurisdictions need to be taken into account.
 
🧑‍🎓
All URLs on this tutorial uses Quaderno Sandbox, our separate environment to learn and experiment.

1. Tax registration

First, you need to set up tax jurisdictions where you're registered for tax filing. This is easily done manually within the App (check Setting up tax jurisdictions ), but here we'll show how to do it programatically. Setting up jurisdictions means you need to associate your Tax IDs (business number) to Tax Jurisdictions. This will define which tax rates are available later for tax calculation!
 
👉
Quaderno will alert you when you reach any tax registration threshold, or in other words, when you need to start filing taxes on that country or region. Learn more about this on the article.

Associating your Tax ID to Tax Jurisdictions

To set up tax jurisdictions programatically you need to use the POST /tax_ids endpoint to associate your business number with the different jurisdiction id's, which you'll need to know first by GET /jurisdictions. Expand for an example.
# Register your Tax ID on a Jurisdiction:
curl --request POST \
  --url http://{{account_name}}.sandbox-quadernoapp.com/api/tax_ids \
  --header 'Authorization: Basic {{private_api_key_base64}}' \
  --data '{
	  "jurisdiction_id": "21",
	  "value": "B35635648"
  }'

# Response:
{
  "id": 234094,
  "jurisdiction": {
    "id": 21,
    "country": "US",
    "name": "United States – New York",
    "region": "NY"
  },
  "state": "verified",
  "valid_from": "2021-09-01",
  "valid_until": null,
  "value": "B35635648",
  "created_at": 1631523250
}
You'll need the Tax Jurisdiction id first. Expand for an example.
# Get all Tax Jurisdictions (note this list is paginated)
curl --request GET \
  --url 'http://{{account_name}}.sandbox-quadernoapp.com/api/jurisdictions/?limit=100' \
  --header 'Authorization: Basic {{private_api_key_base64}}'
  # For Connect, use Authorization: Bearer {{connect_token}}

# Response:
[
  {
    "id": 1,
    "country": "IN",
    "name": "India"
  },
  {
    "id": 2,
    "country": "TR",
    "name": "Turkey"
  },
  {
    "id": 3,
    "country": "CZ",
    "name": "Czech Republic"
  },
# …
 
Each Tax Jurisdiction has many /tax_rates associated. We keep an updated extensive list of taxes worldwide. Once you've registered your Tax ID (business number) on your applicable Tax Jurisdictions, you can list all the different Tax Rates on which the Tax Calculation endpoint /tax_rate/calculate will work for you. Expand for an example.
# List all available Tax Rates based on your registered Tax Jurisdictions:
curl --request GET \
  --url http://{{account_name}}.sandbox-quadernoapp.com/api/tax_rates \
  --header 'Authorization: Basic {{private_api_key_base64}}'

# Response:
[{
    "id": 21,
    "type": "default",
    "jurisdiction": {
      "id": 21,
      "country": "US",
      "name": "United States – New York",
      "region": "NY"
    },
    "name": "Sales tax",
    "tax_code": "saas",
    "valid_from": "2016-01-01",
    "valid_until": null,
    "value": 4.0,
    "created_at": 1630923192
  },
  {
    "id": 20,
    "type": "default",
    "jurisdiction": {
      "id": 21,
      "country": "US",
      "name": "United States – New York",
      "region": "NY"
    },
    "name": "Sales tax",
    "tax_code": "standard",
    "valid_from": "2016-01-01",
    "valid_until": null,
    "value": 4.0,
    "created_at": 1630923192
  },
 
👉
You can use POST /tax_rates to create a "custom Tax Rate" for special needs. You can identify those with a "type": "custom" when you GET /tax_rate/{id} , and a link to it in the field url. Use custom tax rates only as a last resort!
 
Check the API references for /jurisdictions, /tax_ids and /tax_rates for digging into all the details.

2. Tax collection

You legally start collecting taxes for your registered jurisdictions the moment you calculate a tax rate on your checkout and your customer pays you.
 

Calculating Tax Rates on your checkout

🧑‍🎓
While testing out the /tax_rates/calculate endpoint, take into account that results will vary depending on your registered Tax Jurisdictions. Taxes are complex! For the following examples, we include were the account is based and registered on.
 
Example sending customer's location (to_country and to_city) with the total amount, so that we inform you of the appropriate tax rates plus we do all tax calculation amounts. In this case, the tax_code: ebook is not currently taxed in the UK, so it returns a 0.0 rate. Click ‣ to expand.
# UK based business and customer located on UK example
curl --request GET \
  --url 'http://{{account_name}}.sandbox-quadernoapp.com/api/tax_rates/calculate?to_country=UK&amount=100&to_city=London&tax_code=ebook' \
  --header 'Authorization: Basic {{private_api_key_base64}}'

# Response
{
  "country": "UK",
  "currency": "GBP",
  "name": null,
  "notes": null,
  "notice": "",
  "product_type": "service",
  "rate": 0.0,
  "region": null,
  "tax_behavior": "exclusive",
  "tax_code": "ebook",
  "taxable_part": null,
  "import": true,
  "subtotal": 100.0,
  "tax_amount": 0.0,
  "total_amount": 100.0
}
 
Example to only get the tax rates based on your customer's location (to_postal_code and to_country) and the product's tax_code (product type), so that you do all amount calculations on your own. This is specially useful when you have lots of products per sale, as you can perform only 1 API call per checkout. Click ‣ to expand.
# Example to get tax rates and doing amount calculations on your own.
# This is specialy useful when you have lots of products per sale,
# as you can perform only 1 API call per checkout.
curl --request GET \
  --url 'http://{{account_name}}.sandbox-quadernoapp.com/api/tax_rates/calculate?to_postal_code=10128&to_country=US&tax_code=saas&currency=USD' \
  --header 'Authorization: Basic {{private_api_key_base64}}'

# Response for an account based on Rugeley (United Kingdom) and registered on United Stated, New York
{
  "city": "NEW YORK",
  "country": "US",
  "county": "NEW YORK",
  "currency": "USD",
  "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": "service",
  "rate": 8.875,
  "region": "NY",
  "tax_behavior": "exclusive",
  "tax_code": "saas",
  "taxable_part": 100.0,
  "import": true,
  "subtotal": null,
  "tax_amount": null,
  "total_amount": null
}
 
⚠️
Whenever you're trying to calculate taxes and you're not registered on the Tax Jurisdiction you'll get a warning notice:
"notice": "You aren't registered for tax collection in {{tax_jurisdiction}}. If you need to collect taxes there, please add the tax jurisdiction to your Quaderno account.",
 
To understand the /tax_rates/calculate endpoint responses, the formulae are:
taxable_base = amount * taxable_part tax_amount = taxable_base * rate / 100 total_amount = amount + tax_amount
taxable_base is the amount you must use to calculate the tax_amount. It's usually 100% (taxable_part) of the amount of your product before taxes but there are a few exceptions. For example, in Texas the taxable base is 80% for SaaS products.
 
Check the /tax_rates/calculate API Reference for digging into more details.

Validating Tax IDs for B2B transactions (reverse-charge)

You may need to validate if your customer provided a valid tax_id to identify if it's a B2B transaction. You can also provide the tax_id when calculating taxes and we'll add a note "Tax amount subject to reverse charge" in case the business number is registered in the official databases.
 
Example of validating a tax id with the /tax_ids/validate endpoint. When a Tax id is registered on the official databases, we can confirm that it corresponds to a business.
# Example for validating a German Tax id:
curl --request GET \
  --url 'http://{{account_name}}.sandbox-quadernoapp.com/api/tax_ids/validate?country=DE&tax_id=DE303954554' \
  --header 'Authorization: Basic {{private_api_key_base64}}'

# Response:
{
  "valid": true # this means it's a registered business
}
 
🧑‍🎓
On Quaderno Sandbox, all Tax IDs always return { "valid": true } except for:
  • DE111111111: acts as an invalid Tax ID, returning { "valid": false }
  • IE222222222: acts as if the official tax validator service is down, returning  { "valid": null }
 
Reverse-charge example, sending amount and your customer's tax_id (meaning it's a B2B transaction). The API will notify you "Tax amount subject to reverse charge" on the notes field when calling the /tax_rates/calculate endpoint.
# Example sending amount and your customer's Tax ID (meaning it's a B2B transaction)
curl --request GET \
  --url 'http://{{account_name}}.sandbox-quadernoapp.com/api/tax_rates/calculate?to_country=DE&tax_id=DE303954554&amount=100&from_postal_code=35003&tax_code=exempt&from_country=ES' \
  --header 'Authorization: Basic {{private_api_key_base64}}'

# Response for an account based on Spain:
{
  "country": "ES",
  "currency": "EUR",
  "name": null,
  "notes": "Tax amount subject to reverse charge",
  "notice": "You aren't registered for tax collection in Germany. If you need to collect taxes there, please add the tax jurisdiction to your Quaderno account.",
  "product_type": "service",
  "rate": 0.0,
  "region": null,
  "tax_behavior": "exclusive",
  "tax_code": "exempt",
  "taxable_part": null,
  "import": false,
  "subtotal": 100.0,
  "tax_amount": 0.0,
  "total_amount": 100.0
}
 
Check the /tax_ids/validate API Reference for digging into more details.

3. Billing

To create invoices and credit notes for your customers, you can use the older Billing API or the newer Transactions API which is much simpler and saves API calls. We'll use Transactions API here.
 
🧑‍🎓
The act of billing also includes collecting the payment info as well as the evidences used to calculate these taxes, like the IP address of the buyer's device. You can learn more about this topic on Collecting location evidenceCollecting location evidence.
 
One key advantage of Transactions API is that you can just reuse the response from the /tax_rates/calculate endpoint and pass it as a tax object inside eachitem, which makes things much simpler.
Other advantage is that you can use the same API call to record the location evidences (sending an evidence object), create a new customer or reference an existing one (sending a contact object), and log the payment info (sending a payment object), all in just one API call.
 
Example of creating a Transaction with payment info, location evidences, a new contact and two line items with different tax rates. Note that ebooks on NY tax jurisdiction are tax free so the rate is 0.0 for that line item.
# Example of creating a Transaction with payment info, location evidences, 
# a new contact and two line items with different tax rates.
curl --request POST \
  --url http://{{account_name}}.sandbox-quadernoapp.com/api/transactions \
  --header 'Authorization: Basic {{private_api_key_base64}}' \
  --data '{
	"type": "sale",
	"customer": {
		"first_name": "John", 
		"last_name": "Smith",
		"email": "an_email@example.com"
	},
	"evidence": {
		"billing_country": "US",
		"ip_address": "255.255.255.255",
		"bank_country": "US"
	},
	"currency": "USD",
	"items": [
		{
			"description": "Awesome ebook",
			"quantity": 1,
			"amount": 19,
			"tax": {
				"city": null,
				"country": "US",
				"county": null,
				"currency": "USD",
				"name": null,
				"notes": null,
				"notice": "",
				"product_type": "service",
				"rate": 0.0,
				"region": "NY",
				"tax_behavior": "exclusive",
				"tax_code": "ebook",
				"taxable_part": null,
				"import": true,
				"subtotal": null,
				"tax_amount": null,
				"total_amount": null
			}
		},
		{
			"description": "Awesome SaaS",
			"quantity": 1,
			"amount": 89,
			"tax": {
				"city": "NEW YORK",
				"country": "US",
				"county": "NEW YORK",
				"currency": "USD",
				"name": "Sales tax",
				"notes": null,
				"notice": "",
				"product_type": "service",
				"rate": 8.875,
				"region": "NY",
				"tax_behavior": "exclusive",
				"tax_code": "saas",
				"taxable_part": 100.0,
				"import": true,
				"subtotal": null,
				"tax_amount": null,
				"total_amount": null
			}
		}
	], 
	"payment_method": "credit_card",
	"payment": {
		"method": "credit_card",
		"processor": "aPaymentProcessor",
		"processor_id": "ch_xxxxx"
	},
	"po_number": "PO_xxxxxx",
	"tags": "tag-a,tag-b,tag-c",
	"custom_metadata": {
		"anything_you_want": "extra info"
	}
 }'
 
And here it is the response, which fills the gaps on missing info like in the contact object (and ignores others like some of the input we sent on the reused tax object):
{
  "id": 2008034,
  "contact": {
    "id": 1248822,
    "city": null,
    "country": "ES",
    "created_at": 1631884113,
    "email": "an_email@example.com",
    "first_name": "John",
    "full_name": "John Smith",
    "kind": "person",
    "language": "ES",
    "last_name": "Smith",
    "notes": null,
    "permalink": "http://{{acc-name}}.sandbox-quadernoapp.com/billing/{{hash}}?otp=xxx",
    "phone_1": null,
    "postal_code": null,
    "processor": null,
    "processor_id": null,
    "region": null,
    "street_line_1": null,
    "street_line_2": null,
    "tax_id": null,
    "web": null
  },
  "created_at": 1631884142,
  "currency": "USD",
  "custom_metadata": {
    "anything_you_want": "extra info"
  },
  "discount_cents": 0,
  "evidence": {
    "bank_country": "US",
    "billing_country": "US",
    "created_at": 1631884142,
    "ip_address": "255.255.255.255",
    "ip_country": null,
    "state": "confirmed"
  },
  "issue_date": "2021-09-17",
  "items": [
    {
      "id": 5888097,
      "created_at": 1631884142,
      "description": "Awesome ebook",
      "discount_cents": 0,
      "discount_rate": 0.0,
      "product_code": null,
      "quantity": 1.0,
      "subtotal_cents": 1900,
      "total_amount_cents": 1900,
      "unit_price": 19.0
    },
    {
      "id": 5888098,
      "created_at": 1631884142,
      "description": "Awesome SaaS",
      "discount_cents": 0,
      "discount_rate": 0.0,
      "product_code": null,
      "quantity": 1.0,
      "subtotal_cents": 8175,
      "tax_code": "saas",
      "tax_country": "US",
      "tax_name": "Sales tax",
      "tax_rate": 8.875,
      "tax_region": "NY",
      "taxable_part": "100.0",
      "total_amount_cents": 8900,
      "unit_price": 81.75
    }
  ],
  "notes": null,
  "number": "00019",
  "payments": [
    {
      "id": 1410566,
      "amount_cents": 10800,
      "created_at": 1631884142,
      "date": "2021-09-17",
      "payment_method": "credit_card",
      "processor": "aPaymentProcessor",
      "processor_id": "ch_xxxxx"
    }
  ],
  "pdf": "http://sandbox-quadernoapp.com/invoice/{{hash}}.pdf?otp=xxx&q=yyy",
  "permalink": "http://sandbox-quadernoapp.com/invoice/{{hash}}?otp=xxx&q=yyy",
  "po_number": "PO_xxxxxx",
  "processor": null,
  "processor_id": null,
  "state": "outstanding",
  "subtotal_cents": 10075,
  "tags": "tag-a, tag-b, tag-c",
  "taxes": [
    {
      "amount_cents": 725,
      "country": "US",
      "label": "Sales tax (8.875%)",
      "rate": 8.875,
      "region": "NY",
      "tax_code": "saas"
    }
  ],
  "total_cents": 10800,
  "type": "Invoice"
}
 
 
⚠️
No more than 200 document items are allowed in a API request. To add more use subsequent update requests. Maximum items per document are limited up to 1000.
 
Check the /transactions API Reference for digging into all the parameter details.

4. Reporting and filing

Our Reporting API works asynchronously. Every time you need to generate a Quaderno report, you need to create a report request with specific parameters. When the report has finished running, GETting the request id will give you the reference to the file where you can retrieve your results as a CSV file.
 
Example of creating an async Report request and getting the CSV file, extracted from the Quaderno Connect guides (so it uses production URL and a Bearer token to authenticate on behalf of a merchant).
Our Reporting API works asynchronously. This means you'll create a Request object, and then you'll be able to get the report when it's ready with the Request ID provided:
# Making a report request:
curl https://quadernoapp.com/api/reporting/requests \
  -u {{YOUR_SECRET_KEY}}: \
  -H "Authorization: Bearer {{access_token}}"
  -d report_type="tax_summary" \
  -d parameters[from_date]="2021-05-01" \
  -d parameters[from_date]="2021-05-31"

			# Quaderno will respond to that curl with:
			{
			  "id": 456987213, # Use this Request ID to get the Report when it's ready
			  "completed_at": 1612351337,
			  "created_at": 1612351337,
			  "parameters": {
			    "from_date": "2021-05-01",
			    "to_date": "2021-05-31"
			  },
			  "report_type": "tax_summary",
			  "report_url": null,
			  "state": "succeeded"
			}
		
# Finally, get the Report with:
curl https://quadernoapp.com/api/reporting/requests/REQUEST_ID \
  -u {{YOUR_SECRET_KEY}}: \
  -H "Authorization: Bearer {{access_token}}"
 
To avoid polling, we recommend subscribing to our reporting.request.suceeded webhook event, like so:
curl https://quadernoapp.com/api/webhooks \
  -u {{YOUR_SECRET_KEY}}: \
  -H "Authorization: Bearer {{access_token}}" \
  -d url="http://homeowner-app.com/notifications" \
  -d event_types[]="reporting.request.suceeded" \
  -d event_types[]="reporting.request.failed" \
Subscribing to Reporting Requests Example. Check Creating webhooks guide and event types for more info.
 
💡
Send your reports to your tax accountant so that they can file the taxes for you! Here's a list of Quaderno accounting partners you can work with.
 
Check the /reporting API Reference for digging into more details.