Taxation Rules

7 views Markdown

The seven endpoints for tax rates, country rules and the invoice document settings.

Overview

Tax is built in three layers. At the bottom sit the basic settings. They say whether tax is on, what the default rate is, and where the tax sits.

Above them stand the country and state rules. When a rule matches a client's address the rate comes from there. A state rule comes before a country rule, and a country rule before the default.

A separate group covers the invoice document itself. It shapes the numbering, opens or closes formalising, names the tax fields a company client is asked for, and handles posting a printed bill.

Reference

Reading the Tax Settings

get/api/v1/admin/financial/taxation
Financial/GetTaxation admin

Returns whether tax is on, its rate and how it is worked out.

Response fields data — 4
enabledboolWhether tax is on.
ratefloatThe default tax rate.
taxation_typestringWhether tax sits inside the amount or on top of it.
send_bill_to_addressobjectPosting a printed bill: whether it is on, its charge and its currency.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/financial/taxation' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/financial/taxation', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/taxation');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The rate here is the DEFAULT; a rule set for the client's country comes first.
$t = Api::Financial()->GetTaxation()['data'];

Writing the Tax Settings

put/api/v1/admin/financial/taxation
Financial/UpdateTaxation admin

Switches tax on and writes its rate and how it is worked out.

Body 3
enabledboolSwitches tax on or off.
ratefloatThe default tax rate.
taxation_typestringWhether tax sits inside the amount or on top of it.
Response fields data — 4
dataobjectThe tax settings as they now stand. Same shape as the read endpoint.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/financial/taxation' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"enabled":true,"rate":20,"taxation_type":"exclusive"}'
const res = await fetch('https://panel.example.com/api/v1/admin/financial/taxation', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    enabled: true,
    rate: 20,
    taxation_type: 'exclusive',
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/taxation');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['enabled' => true, 'rate' => 20]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Changing how tax is worked out turns the SAME price into a different total; think again
// about what the prices you advertise are meant to say.
Api::Financial()->UpdateTaxation(['taxation_type' => 'inclusive']);

Reading the Advanced Settings

get/api/v1/admin/financial/taxation/advanced
Financial/GetTaxationAdvanced admin

Returns the settings for invoice numbering and the company tax fields.

Response fields data — 18
invoice_show_requires_loginboolWhether seeing an invoice needs signing in.
payment_commission_taxboolWhether the payment commission and the instalment surcharge carry the invoice's rate.
delete_invoice_item_aocboolWhether an invoice line can be removed.
invoice_formalization_statusboolWhether formalising is available.
firstly_create_invoiceboolWhether the invoice is cut before the order.
balance_taxationstringHow topping up the balance gets taxed.
invoice_special_notestringA note put on every invoice.
pdf_fontstringThe typeface used in the document.
invoice_number_formatstringThe invoice number's shape. It has to carry the placeholder marking where the number goes.
invoice_number_format_statusboolWhether that shape is used.
paid_invoice_number_formatstringThe paid-invoice number's shape.
paid_invoice_number_format_statusboolWhether that shape is used.
invoice_incrementintThe number invoice numbering starts from.
paid_invoice_incrementintThe number paid-invoice numbering starts from.
send_bill_to_addressobjectPosting a printed bill: whether it is on, its charge and its currency.
company_tax_officeobjectThe tax office field: whether it shows and whether it is required.
company_tax_numberobjectThe tax number field: whether it shows, whether it is required and whether it is checked.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/financial/taxation/advanced' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/financial/taxation/advanced', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/taxation/advanced');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// There are two numbering series: one for invoices and one for PAID invoices.
$a = Api::Financial()->GetTaxationAdvanced()['data'];

Writing the Advanced Settings

put/api/v1/admin/financial/taxation/advanced
Financial/UpdateTaxationAdvanced admin

Changes the advanced tax and invoice settings you send.

Body 18
invoice_show_requires_loginboolWhether seeing an invoice needs signing in.
payment_commission_taxboolWhether the payment commission and the instalment surcharge carry the invoice's rate.
delete_invoice_item_aocboolWhether an invoice line can be removed.
invoice_formalization_statusboolWhether formalising is available.
firstly_create_invoiceboolWhether the invoice is cut before the order.
balance_taxationstringHow topping up the balance gets taxed.
invoice_special_notestringA note put on every invoice.
pdf_fontstringThe typeface used in the document.
invoice_number_formatstringThe invoice number's shape. It has to carry the placeholder marking where the number goes.
invoice_number_format_statusboolWhether that shape is used.
paid_invoice_number_formatstringThe paid-invoice number's shape.
paid_invoice_number_format_statusboolWhether that shape is used.
invoice_incrementintThe number invoice numbering starts from.
paid_invoice_incrementintThe number paid-invoice numbering starts from.
send_bill_to_addressobjectPosting a printed bill: whether it is on, its charge and its currency.
company_tax_officeobjectThe tax office field: whether it shows and whether it is required.
company_tax_numberobjectThe tax number field: whether it shows, whether it is required and whether it is checked.
Response fields data — 18
dataobjectThe settings as they now stand. Same shape as the read endpoint.
Errors 2
invalid_number_format422The number format carries no placeholder.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/financial/taxation/advanced' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"invoice_number_format":"FTR-{NUMBER}","invoice_number_format_status":true}'
const res = await fetch('https://panel.example.com/api/v1/admin/financial/taxation/advanced', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    invoice_number_format: 'INV-{NUMBER}',
    invoice_number_format_status: true,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/taxation/advanced');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'invoice_number_format'        => 'INV-{NUMBER}',
        'invoice_number_format_status' => true,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Lowering the numbering START can hand the same number out twice; do not drop below
// the highest number already given.
Api::Financial()->UpdateTaxationAdvanced(['invoice_increment' => 1000]);

Listing the Tax Rules

get/api/v1/admin/financial/tax-rules
Financial/GetTaxRules admin

Returns the tax rates set by country and state.

Response fields data[] — 7
country_idintThe country id.
country_namestringThe country name.
ccstringThe two-letter country code.
state_idintThe state id. Zero says the rule covers the whole country.
state_namestringThe state name.
tax_ratefloatThe total rate. The parts added together.
ratesobject[]The parts making up the rate: each with its name and value.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/financial/tax-rules' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/financial/tax-rules', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/tax-rules');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A state rule comes before the COUNTRY rule; with neither, the default rate applies.
$rules = Api::Financial()->GetTaxRules()['data'];

Writing a Tax Rule

put/api/v1/admin/financial/tax-rules
Financial/UpdateTaxRule admin

Writes the tax rate for a country or a state.

Body 4
country_idintreqThe country the rule covers.
state_idintThe state the rule covers. Zero means the whole country.
state_namestringThe name of a new state. Given, the state is created first.
ratesobject[]The parts making up the rate: each with its name and value.
Response fields data — 3
country_idintThe rule's country.
state_idintThe rule's state. A newly created state comes back with its id here.
ratefloatThe total rate worked out.
Errors 5
invalid_country422The country is not valid.
state_insert_failed422The new state could not be created.
tax_rule_failed422The rule could not be written.
blocked_by_gate422A hook refused the save.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/financial/tax-rules' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"country_id":792,"state_id":0,"rates":[{"name":"KDV","value":20}]}'
const res = await fetch('https://panel.example.com/api/v1/admin/financial/tax-rules', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    country_id: 840,
    state_id: 0,
    rates: [{ name: 'VAT', value: 20 }],
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/tax-rules');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'country_id' => 840,
        'rates'      => [['name' => 'VAT', 'value' => 20]],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The parts REPLACE what was there: your list clears the old one rather than adding.
Api::Financial()->UpdateTaxRule([
    'country_id' => 840,
    'rates'      => [['name' => 'VAT', 'value' => 20]],
]);

Defining the European Rates

post/api/v1/admin/financial/tax-rules/define-all
Financial/DefineAllTaxRates admin it writes over existing rules

Defines the value-added tax rates of the European Union countries at once.

Body
No body is needed. The rate set is built in rather than sent, so there is nothing to pick; send an empty body.
Response fields data — 1
definedintHow many rates were defined.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/financial/tax-rules/define-all' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/financial/tax-rules/define-all', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const { data } = await res.json();
console.log(data.defined);
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/tax-rules/define-all');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// If the installation's own country is on the list the DEFAULT rate moves; keep the old
// rules first.
$before = Api::Financial()->GetTaxRules()['data'];
Api::Financial()->DefineAllTaxRates();

Pitfalls

A rate can come from three places

The rate on an invoice comes first from the client's state rule. Failing that it comes from the country rule, and only then from the default. Changing the default moves nothing for clients in countries that carry a rule. That is usually behind a "the rate update did nothing" report.

The rule parts replace what was there

When writing a tax rule the list of parts you send replaces what was there rather than adding to it. To add a second part to a country, read the current list and send both together. Sending one part quietly drops the others.

The bulk define writes over existing rules

Defining the European rates in bulk writes over the rules you entered by hand for those countries. When the installation's own country is among them, the default rate moves as well. Read and keep the current rules before running it, because there is no other way back.

Lowering the numbering start causes clashes

Dropping the invoice numbering start below the highest number already given hands the same number out twice. Two documents sharing a number is a serious matter in accounting and hard to put right afterwards. Move the start upward alone.

How tax is worked out changes the total

Whether tax sits inside the amount or on top of it changes what your advertised prices say. Inside, an item marked a hundred sells for a hundred with the tax taken out of it. On top, the client pays a hundred and twenty. Changing this once an installation is live means going back over the whole price list.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.