> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waffo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Preview Tax

> Query tax rules and calculate tax for a checkout session, progressively

Query the tax rules that apply to a checkout session and, once enough billing information is available, get the calculated tax amount.

This endpoint is designed for **progressive collection**: only `billingDetail.country` is required. The response tells you which additional fields that country needs via `rules.requiredFields`, so the checkout form can ask for them one step at a time instead of demanding everything upfront.

```
POST /v1/actions/checkout/preview-tax
```

**Authentication:** Store Slug (`visitor` role)

<Note>
  This endpoint is called by the hosted checkout page as the customer fills in their billing details. If you are building your own checkout UI, call it whenever the customer's country or business status changes.
</Note>

## Request Body

| Field               | Type   | Required | Description                                                                                               |
| ------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------- |
| `checkoutSessionId` | string | Yes      | Session ID returned by [Create Checkout Session](/api-reference/endpoints/orders/create-checkout-session) |
| `billingDetail`     | object | Yes      | Billing information collected so far (see below)                                                          |

### billingDetail

| Field          | Type    | Required | Description                                             |
| -------------- | ------- | -------- | ------------------------------------------------------- |
| `country`      | string  | Yes      | ISO 3166-1 alpha-2 country code (e.g. `US`, `DE`, `JP`) |
| `isBusiness`   | boolean | No       | Whether the customer is buying as a business            |
| `state`        | string  | No       | State / province code                                   |
| `postcode`     | string  | No       | Postal code                                             |
| `businessName` | string  | No       | Legal business name                                     |
| `taxId`        | string  | No       | Business tax ID (e.g. EU VAT number)                    |

Only `country` is required. **Which of the remaining fields you need to collect is decided by the response**, not by a fixed rule per country — see `rules.requiredFields`.

## Response

| Field         | Type           | Description                                                     |
| ------------- | -------------- | --------------------------------------------------------------- |
| `currency`    | string         | Currency of the checkout session                                |
| `rules`       | object         | Tax rules for the given country                                 |
| `calculation` | object \| null | Calculated amounts, or `null` when more fields are still needed |

### rules

| Field                       | Type    | Description                                                  |
| --------------------------- | ------- | ------------------------------------------------------------ |
| `country`                   | string  | Country code the rules apply to                              |
| `taxName`                   | string  | Name of the tax (e.g. `JCT`, `VAT`)                          |
| `standardRate`              | number  | Standard rate as a decimal (`0.1` = 10%)                     |
| `isTaxFree`                 | boolean | Whether this country is tax-exempt                           |
| `isEuReverseChargeEligible` | boolean | Whether EU reverse charge can apply                          |
| `hasReducedRates`           | boolean | Whether reduced rates exist for some product categories      |
| `requiredFields`            | array   | Fields still needed before tax can be calculated (see below) |

### requiredFields

Each entry describes one field the checkout form should collect next:

| Field       | Type   | Description                                                       |
| ----------- | ------ | ----------------------------------------------------------------- |
| `field`     | string | One of `isBusiness`, `state`, `postcode`, `businessName`, `taxId` |
| `label`     | string | Human-readable label for the input                                |
| `reason`    | string | Why this country needs it — safe to show to the customer          |
| `inputType` | string | Suggested input control                                           |

When `requiredFields` is empty, `calculation` is populated.

### calculation

| Field       | Type   | Description                                          |
| ----------- | ------ | ---------------------------------------------------- |
| `subtotal`  | string | Amount before tax, in display format (e.g. `"8.39"`) |
| `taxAmount` | string | Tax amount, in display format                        |
| `total`     | string | Amount the customer will be charged                  |
| `taxRate`   | number | Rate applied to this order                           |

<Note>
  Amounts are display-format strings, matching the rest of the API. `taxRate` is a decimal (`0.19` = 19%).
</Note>

## Examples

<CodeGroup>
  ```bash More information needed theme={"system"}
  curl -X POST https://api.waffo.ai/v1/actions/checkout/preview-tax \
    -H "X-Store-Slug: my-store-abc123" \
    -H "X-Environment: prod" \
    -H "Content-Type: application/json" \
    -d '{
      "checkoutSessionId": "cs_xxx",
      "billingDetail": { "country": "US" }
    }'
  ```

  ```bash Ready to calculate theme={"system"}
  curl -X POST https://api.waffo.ai/v1/actions/checkout/preview-tax \
    -H "X-Store-Slug: my-store-abc123" \
    -H "X-Environment: prod" \
    -H "Content-Type: application/json" \
    -d '{
      "checkoutSessionId": "cs_xxx",
      "billingDetail": { "country": "DE", "isBusiness": false }
    }'
  ```
</CodeGroup>

More information needed — the form should now ask for `state`:

```json theme={"system"}
{
  "data": {
    "currency": "USD",
    "rules": {
      "country": "US",
      "taxName": "Sales Tax",
      "standardRate": 0,
      "isTaxFree": false,
      "isEuReverseChargeEligible": false,
      "hasReducedRates": false,
      "requiredFields": [
        {
          "field": "state",
          "label": "State",
          "reason": "Sales tax in the United States is determined at state level",
          "inputType": "select"
        }
      ]
    },
    "calculation": null
  }
}
```

Ready to calculate:

```json theme={"system"}
{
  "data": {
    "currency": "EUR",
    "rules": {
      "country": "DE",
      "taxName": "VAT",
      "standardRate": 0.19,
      "isTaxFree": false,
      "isEuReverseChargeEligible": true,
      "hasReducedRates": true,
      "requiredFields": []
    },
    "calculation": {
      "subtotal": "8.39",
      "taxAmount": "1.60",
      "total": "9.99",
      "taxRate": 0.19
    }
  }
}
```

## Errors

| Status | Message                                            | Cause                                                                                                    |
| ------ | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| 400    | `Missing required field: checkoutSessionId`        | `checkoutSessionId` not provided                                                                         |
| 400    | `Missing required field: billingDetail.country`    | `billingDetail` missing or has no `country`                                                              |
| 400    | `Missing or invalid header: x-context-environment` | `X-Environment` header missing or not `test` / `prod`                                                    |
| 404    | `Checkout session not found or expired`            | Session does not exist, has expired, belongs to another store, or was created in a different environment |
| 500    | `Internal server error`                            | Unexpected server-side failure                                                                           |

## Current tax coverage

Tax determination runs off the customer's billing location, and Waffo Pancake carries the tax obligation as Merchant of Record. With the coverage configured today, **`taxAmount` and `taxRate` come back as `0` for every country** — this endpoint is the authoritative source for what a given order will actually be charged, so read `calculation.total` rather than assuming a rate.

`rules.requiredFields` is likewise driven by live configuration. Do not hard-code "US and CA need state" or "EU businesses need a tax ID" in your checkout UI — render whatever the response asks for.
