# Explain a tax decision

> The tax.quotes.explain operation: GET /api/v1/tax/quotes/{id}/explanation in the REST API and the explain_tax_decision tool in the MCP server.

Explains a stored tax quote in plain words: why the customer counts as a business or a consumer, where they
are located and how sure that is, which tax regime applies and why, where the sale is taxed, what the
registration state allowed (charged, below the threshold, reverse charge, blocked), which rate applies, and
how the amounts were computed (spec §9.1). `notes` add what else matters: the invoice wording needed,
unverified rules, registration changes the sale would trigger.

The text is written only from Ownrecord's own templates and the quote's codes, never from text a customer
or integrator typed: lines are named by their position (`lines[].index`), with their `id` beside the text.
`ruleVersions` names every rule version used, so the decision can be replayed. The full rule trace is in
`tax.quotes.get`.

## At a glance

- **REST:** `GET /api/v1/tax/quotes/{id}/explanation`
- **MCP tool:** `explain_tax_decision`
- **Operation ID:** `tax.quotes.explain`
- **Scope:** Brand
- **Permission:** `checkout:read`
- **Risk tier:** `read`
- **Dry run:** Not supported
- **Idempotency:** Not needed (read only)
- **Modes:** Test and live
- **Authentication:** API key with the `checkout:read` scope
- **Since:** M10

## Request

### Path parameters

| Field | Type      | Description              |
| ----- | --------- | ------------------------ |
| `id`  | `tq_…` ID | The quote's ID (`tq_…`). |

### Query parameters

This operation takes no query parameters.

## Response

`200 OK` with a JSON object:

| Field                          | Type                                                                                          | Description |
| ------------------------------ | --------------------------------------------------------------------------------------------- | ----------- |
| `object`                       | `"tax_explanation"`                                                                           |             |
| `quoteId`                      | `tq_…` ID                                                                                     |             |
| `livemode`                     | boolean                                                                                       |             |
| `decision`                     | one of `charge`, `reverse_charge`, `exempt`, `out_of_scope`, `blocked`                        |             |
| `summary`                      | string                                                                                        |             |
| `steps`                        | array of object                                                                               |             |
| `steps[].step`                 | integer (1 to 9)                                                                              |             |
| `steps[].name`                 | one of `customer`, `location`, `regime`, `place_of_supply`, `registration`, `rate`, `amounts` |             |
| `steps[].title`                | string                                                                                        |             |
| `steps[].text`                 | string                                                                                        |             |
| `lines`                        | array of object                                                                               |             |
| `lines[].index`                | integer (at least 0)                                                                          |             |
| `lines[].id`                   | string                                                                                        |             |
| `lines[].text`                 | string                                                                                        |             |
| `notes`                        | array of string                                                                               |             |
| `ruleVersions`                 | object                                                                                        |             |
| `ruleVersions.rateIds`         | array of string                                                                               |             |
| `ruleVersions.regimeId`        | string or null                                                                                |             |
| `ruleVersions.regimeVersion`   | string or null                                                                                |             |
| `ruleVersions.knowledgeCommit` | string                                                                                        |             |

## Examples

### A consumer in Germany, below the EU threshold

```http
GET /api/v1/tax/quotes/tq_01m4tx5nj8emwbyf7y9vcf47xq/explanation
Authorization: Bearer or_test_…
```

Response (`200`):

```json
{
  "object": "tax_explanation",
  "quoteId": "tq_01m4tx5nj8emwbyf7y9vcf47xq",
  "livemode": false,
  "decision": "charge",
  "summary": "Tax is charged: EUR 25.00 on a net EUR 100.00 (EUR 125.00 in total), under Croatia, domestic VAT (HR_DOMESTIC).",
  "steps": [
    {
      "step": 1,
      "name": "customer",
      "title": "Who the customer is",
      "text": "The customer gave no tax ID, so the sale is treated as a sale to a consumer (business-to-consumer)."
    },
    {
      "step": 2,
      "name": "location",
      "title": "Where the customer is",
      "text": "The customer is located in DE: all the location evidence agrees (2 evidence items: billing address, IP address)."
    },
    {
      "step": 3,
      "name": "regime",
      "title": "Which tax regime applies",
      "text": "The regime for this sale is One-Stop Shop, Union scheme (EU_OSS_UNION), chosen from where the seller is established, where the customer is and the customer's type."
    },
    {
      "step": 5,
      "name": "place_of_supply",
      "title": "Where the sale is taxed",
      "text": "Every line is taxed where the customer is."
    },
    {
      "step": 6,
      "name": "registration",
      "title": "What the registration state allows",
      "text": "The entity's sales under the regime are below its registration threshold, so the seller's home-country rules apply instead. The tax is charged under Croatia, domestic VAT (HR_DOMESTIC) instead of One-Stop Shop, Union scheme (EU_OSS_UNION)."
    },
    {
      "step": 7,
      "name": "rate",
      "title": "Which rate applies",
      "text": "Line 1: the standard rate of 25% in HR for the tax category `saas`. The rates are the ones valid at the tax point."
    },
    {
      "step": 8,
      "name": "amounts",
      "title": "How the amounts were computed",
      "text": "Prices exclude tax, so the tax is added to the net amount: net EUR 100.00, tax EUR 25.00, gross EUR 125.00."
    }
  ],
  "lines": [
    {
      "index": 0,
      "id": "pro_monthly",
      "text": "Line 1 (tax category `saas`): tax is charged; net EUR 100.00, tax EUR 25.00, gross EUR 125.00."
    }
  ],
  "notes": [],
  "ruleVersions": {
    "rateIds": ["kn_01m4tx5nj8emwbyf7y9vcf47xs"],
    "regimeId": "EU_OSS_UNION",
    "regimeVersion": "10fc4013168eb593",
    "knowledgeCommit": "f101d33bdd0b1b9257b6b78826836646a30e4fa5"
  }
}
```

### A business in Austria: reverse charge

```http
GET /api/v1/tax/quotes/tq_01m4tx5nj8emwbyf7y9vcf47xr/explanation
Authorization: Bearer or_test_…
```

Response (`200`):

```json
{
  "object": "tax_explanation",
  "quoteId": "tq_01m4tx5nj8emwbyf7y9vcf47xr",
  "livemode": false,
  "decision": "reverse_charge",
  "summary": "No tax is charged under One-Stop Shop, Union scheme (EU_OSS_UNION): the business customer accounts for it (reverse charge).",
  "steps": [
    {
      "step": 1,
      "name": "customer",
      "title": "Who the customer is",
      "text": "The customer is a business: its tax ID from AT was checked and is valid, so the sale is business-to-business."
    },
    {
      "step": 2,
      "name": "location",
      "title": "Where the customer is",
      "text": "The customer is located in AT: it is the country of the business's validated tax ID."
    },
    {
      "step": 3,
      "name": "regime",
      "title": "Which tax regime applies",
      "text": "The regime for this sale is One-Stop Shop, Union scheme (EU_OSS_UNION), chosen from where the seller is established, where the customer is and the customer's type."
    },
    {
      "step": 5,
      "name": "place_of_supply",
      "title": "Where the sale is taxed",
      "text": "Every line is taxed where the customer is."
    },
    {
      "step": 6,
      "name": "registration",
      "title": "What the registration state allows",
      "text": "The customer is a business in another country: the customer accounts for the tax (reverse charge), and the entity needs no registration for it."
    },
    {
      "step": 7,
      "name": "rate",
      "title": "Which rate applies",
      "text": "No rate was needed: no line is charged tax."
    },
    {
      "step": 8,
      "name": "amounts",
      "title": "How the amounts were computed",
      "text": "Prices exclude tax, so the tax is added to the net amount: net EUR 100.00, tax EUR 0.00, gross EUR 100.00."
    }
  ],
  "lines": [
    {
      "index": 0,
      "id": "1",
      "text": "Line 1 (tax category `saas`): no tax is charged: the business customer accounts for it (reverse charge); net EUR 100.00, tax EUR 0.00, gross EUR 100.00."
    }
  ],
  "notes": ["The invoice must say that the customer accounts for the VAT (reverse charge)."],
  "ruleVersions": {
    "rateIds": [],
    "regimeId": "EU_OSS_UNION",
    "regimeVersion": "10fc4013168eb593",
    "knowledgeCommit": "f101d33bdd0b1b9257b6b78826836646a30e4fa5"
  }
}
```

## Errors

| Code                                                                                         | HTTP status | When                                                                                 | What to do                                                                                                                                         |
| -------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)       | 400         | `id` isn't a tax quote ID (`tq_…`).                                                  | Fix the fields listed in `details.issues` and send the request again.                                                                              |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)     | 404         | This brand has no such quote in this mode (quotes belong to one brand and one mode). | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                               |
| [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required)             | 400         | The request names no brand (an org or entity key without `Ownrecord-Brand`).         | Call the operation with credentials or a target at its level: an organization, a legal entity, a brand or the platform (`details.required_scope`). |
| [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed) | 403         | You have no live-mode access, so you can't read live quotes.                         | Use test mode, or credentials for the target's mode. Developers need live access granted by an organization owner or admin.                        |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)       | 403         | You can't read this brand's checkout data (`checkout:read`).                         | Ask an organization owner or admin for a role or scope that allows this action.                                                                    |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`dry_run_not_supported`](https://ownrecord.io/docs/reference/errors.md#dry_run_not_supported), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
