# Create tax code

`POST /api/configuration/tax-codes`

- **Products:** Operator (`FOH`)
- **Features:** Front of House Generic Access (`FOHGENERIC`)

HTML version: [Create tax code](https://support.gladstonesoftware.cloud/reference/configuration/create-tax-code)

## Overview

Creates a tax code — a named tax rate that products and invoice lines are charged under. A tax code holds a
description, a percentage rate and an optional reference; it does not know anything about jurisdictions or
which products use it. Products carry the tax code's id, and each invoice line records both the id and the
rate at the moment it is quoted, so **changing a rate later does not restate anything already
quoted or invoiced**. If a rate changes, create a new tax code rather than editing the old one.

The identifier is a short string. You choose it yourself by sending `id`, or you omit `id` and one is allocated
for you: the endpoint takes the highest existing id beginning `TAXC`, adds one, and zero-pads the number to a
total length of 15 characters, giving ids of the form `TAXC00000000001`. Either way the id that was used
comes back in the response body:

```json title="201 Created"
{ "id": "TAXC00000000001" }
```

Read that property case-insensitively, and take the id from it rather than assuming the value you sent was
stored verbatim — an id you supply is upper-cased and has its spaces removed first. The endpoint also sets a
`Location` header.

Retrying is only safe when you choose the id yourself: a second request with an id that already exists is
rejected with `409 Conflict` and nothing is saved. If you omit `id`, each retry allocates a new id and
creates another tax code — there is no duplicate detection on `description` or `rate`, and nothing stops you
holding two tax codes at 20%.

The id is the only thing that has to be unique. Note that its 15-character limit is not checked by request
validation on this endpoint, so an over-long id is not caught by validation and
fails as a `500` instead of a `400`.

## Endpoint

`POST {base_url}/api/configuration/tax-codes`

`{base_url}` is the environment URL issued to your integration. See [Authenticating with Gladstone APIs](../../guides/authenticating-with-gladstone-apis.md).

## Request body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | No | The identifier you want the tax code to have. It is upper-cased and has spaces stripped before storage, so `vat std` is saved and must later be referenced as `VATSTD`, and it must not already be in use. Unlike most ids on this API it is not length-checked by request validation, but the maximum is 15 characters, so anything longer fails with `500` rather than `400`. Omit the field and the endpoint allocates the next id in the `TAXC…` sequence and returns it to you. |
| `description` | string | Yes | The name of the tax code as it appears to staff — "Standard rate", "Zero rated" and so on. Must not be empty and must be 25 characters or fewer. The rejection message for this field reads `The price level description cannot be more than 25 characters`; that mention of price levels is an error in the message, which does refer to this field. |
| `rate` | number (double) | No | The tax percentage, expressed as a number out of 100 rather than a fraction: send `20` for 20%, not `0.2`. It must be between 0 and 100 inclusive and must have no more than two decimal places — `17.5` is accepted, `17.501` is rejected with `400`. `0` is valid and is how you record a zero-rated code. Omitting the field is the same as sending `0`, so a tax code created without a rate charges nothing. The value is stored with single precision, so do not expect exact round-tripping of values beyond two decimal places. |
| `reference` | string | No | A free-text reference of up to 25 characters stored alongside the tax code and returned by the retrieval endpoints. Defaults to an empty string when omitted. Its rejection message also mentions "price level reference", which is the same error. |
| `translations` | array of object | No | Translations of this tax code's `description` for bilingual sites. Omit or send an empty array if you only need the `description`. Entries are stored as supplied without any validation, so a mistake here surfaces as a `500` rather than a `400`. |
| `translations[].type` | string | No | The name of the field being translated. Use `description` — that is the only property the tax code retrieval and list endpoints read back, so an entry with any other value is stored but never returned. |
| `translations[].culture` | string | No | The culture code the translation is for, for example `cy-GB`. It must be a culture code the platform already knows about; an unrecognised code fails the request with `500`. |
| `translations[].text` | string | No | The translated text itself — what `description` should read as in that culture. |

## Sample request

```bash title="cURL"
curl -X POST '{base_url}/api/configuration/tax-codes' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "id": "string",
  "description": "string",
  "rate": 10.5,
  "reference": "string",
  "translations": [
    {
      "type": "string",
      "culture": "string",
      "text": "string"
    }
  ]
}'
```

## Errors

Request validation failures return a validation problem document (`application/problem+json`) with an
`errors` map keyed by field name. The other failures return the message below as a plain string body.

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | Request validation failed before any other check: `description` missing, empty or longer than 25 characters (`The description must not be empty`, `The price level description cannot be more than 25 characters`), `rate` outside 0–100 (`Rate has to be between 0 and 100 inclusive`), or `reference` longer than 25 characters (`The price level reference cannot be more than 25 characters`). |
| `400` | Bad request | Malformed JSON, or a value of the wrong type — including `null` sent for `rate`, which is not a nullable field. |
| `400` | Bad request | `rate` has more than two decimal places. Body: `The tax rate can only be specified to two decimal places`. This is checked before anything else, so it masks a duplicate id. |
| `409` | Conflict | The `id` you supplied is already in use, compared after upper-casing and removing spaces. Body: `The TaxCode {id} already exists`. Nothing is created. |
| `409` | Conflict | A concurrent request created a tax code with the same id at the same moment. Body: `This entry already exists in the database`. |
| `500` | Internal server error | The `id` you supplied is longer than 15 characters. Request validation does not check this, so it fails when the tax code is saved. |
| `500` | Internal server error | A `translations[].culture` is not a culture code the platform recognises, so the translation cannot be saved. |
| `500` | Internal server error | Any other unexpected server-side failure. |
