> This page is for version v5.0.
> For other versions, use one of these documentation indexes:
> - v6.0 (default): https://docs.zip.tax/v-6-0/llms.txt
> - v5.0: https://docs.zip.tax/v-5-0/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.zip.tax/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.zip.tax/_mcp/server.

# Calculate Cart Tax

POST https://api.zip-tax.com/calculate/cart
Content-Type: application/json

Calculates sales tax for one cart of up to 250 line items with the Ziptax rate engine. Nothing is stored: the returned cartId correlates the response with the request only.

**Sourcing.** The destination is resolved first and its sourcing rule decides where the cart is quoted: at the destination, or at the origin when the destination's rule is origin-based and the origin resolves into the same state. Origin sourcing never crosses a state line. A product line's own rate rules can override the cart's sourcing: the most granular state, statewide, county or conditional rule that sets isDestinationTaxType quotes the line at the destination (true) or the origin (false), still never across a state line.

**Lines.** General goods (no taxabilityCode, or 0) are quoted at the SALES_TAX total of the address the cart is quoted at. Shipping (10001) and handling (11000) follow that address's shipping taxability: Y taxes both, N neither, L handling only. Any other code is a product code and needs the product_rates entitlement. It is quoted with its product rate rules: the most granular rule that carries a value applies to the whole rate, and a place or tax-code rule applies once to the summed rate of the components it covers; a rule exempts the line when price x quantity, rounded to cents, is strictly above exemptOver or strictly below exemptUnder, and otherwise taxes the amount above taxablePortionOver, times percentTaxable, at effectiveTaxRate or the rate it applies to. Prepared food (41000, 41041) is quoted from its meal tax when meal tax applies, which takes precedence over the rate rules: a STATE_SPECIAL_TOTAL component replaces all sales tax, ADD_ON components are added on top, and an OCCUPATION_TAX component is added and the sales tax is charged on it too. tax.rate is the total rate the line collects.

**transactionDate.** Optional RFC 3339 date-time, defaulting to now. A date in a past month is quoted from that month's historical rates, with rate rules and meal tax dated on the exact day in the date's own offset. The current month or a later one is quoted from current rates. A past month whose tax_table rates the running service has not loaded uses the current tax_table rates, while SST states and other dated sources keep the month, and rate rules and meal tax stay dated on its own day. A date before the historical rates start, or one that is not RFC 3339, is rejected with 400 before any address is resolved. The response echoes it.

**Failures.** A destination that cannot be resolved fails the cart with 422 and is never quoted at the origin, which can be in another state. An origin that cannot be resolved falls back to the destination. A rate response without a SALES_TAX total fails with 500 rather than quoting 0%. A failed rate lookup names the line item it failed on.

**Billing.** Each distinct address the cart resolved is one geo request, however many taxability codes it is resolved with, and an address that resolved is billed even when a later line fails. The origin is resolved only when the cart or a line is sourced there, or for a prepared-food line, whose seller-located meal tax comes from it, unless the destination's meal tax already reports RULES_NOT_LOADED, LOCATION_NOT_RESOLVED or STATE_NOT_COVERED and the line is not due at the origin. The addresses are free-form strings, so the origin's state is known only once it resolves: an origin-sourced cart whose origin is in another state never resolves it again for a line, while on a destination-sourced cart such a line resolves, and bills, the origin to learn its state, even when it turns out to be in another state.

Reference: https://docs.zip.tax/api-reference/ziptax-api/cart/calculate-cart

## Authentication

- `X-API-KEY` header (required) — API Key authentication via header

## Request

### Body (application/json)

This endpoint expects an object.

- `items` (list of CartInput, required) — The cart to calculate. Exactly one cart per request.
- `transactionDate` (datetime, optional) — RFC 3339 date-time the sale took place at. Defaults to now. A date in a past month is quoted from that month's historical rates, with product rate rules and meal tax dated on the exact day in the date's own offset; the current month or a later one is quoted from current rates. A past month whose tax_table rates the running service has not loaded uses the current tax_table rates, while SST states and other dated sources keep the month, and rate rules and meal tax stay dated on its own day. A date before the historical rates start is rejected with 400.

## Response

### 200

OK

- `items` (list of CartOutput, required) — The calculated cart.
- `transactionDate` (datetime, required) — The request's transactionDate as sent, trimmed, or the time the cart was calculated, in RFC 3339 UTC, when none was sent.

## Errors

### 400 Calculate Cart Request Bad Request Error

The request failed validation: a body that does not bind (error `Invalid request`, with details), more or fewer than one cart, no line items or more than 250, or a transactionDate that is not RFC 3339 or is older than the historical rates (error `Invalid request`, with details). Also a rate lookup the engine rejected as a malformed query or historical period (v60 105, 111), with details and itemId.

- `error` (string, required) — What went wrong. For a failed rate lookup it comes from the same mapping /merchant/cart/calculate uses; branch on it and the HTTP status.
- `details` (string, optional) — For a failed rate lookup, the message this endpoint has always published for the v60 code behind it. Kept for integrations that match on it, but for several codes it names an unrelated cause. For a request that does not bind or a bad transactionDate, why it was rejected.
- `itemId` (string, optional) — The line item a failed rate lookup failed on: the first line when the cart's own sourcing failed.
- `message` (string, optional) — Sent by the endpoint's per-minute rate limit and by an account store failure.
- `status` (string, optional) — The account status, on a 401 for an invalid or inactive key.

### 401 Calculate Cart Request Unauthorized Error

No X-API-KEY header (`Missing X-API-KEY header`), an invalid or inactive key (`Invalid or inactive API key`, with status), or a key the rate engine rejected (v60 101, `Invalid API key`, with details and itemId).

- `error` (string, required) — What went wrong. For a failed rate lookup it comes from the same mapping /merchant/cart/calculate uses; branch on it and the HTTP status.
- `details` (string, optional) — For a failed rate lookup, the message this endpoint has always published for the v60 code behind it. Kept for integrations that match on it, but for several codes it names an unrelated cause. For a request that does not bind or a bad transactionDate, why it was rejected.
- `itemId` (string, optional) — The line item a failed rate lookup failed on: the first line when the cart's own sourcing failed.
- `message` (string, optional) — Sent by the endpoint's per-minute rate limit and by an account store failure.
- `status` (string, optional) — The account status, on a 401 for an invalid or inactive key.

### 403 Calculate Cart Request Forbidden Error

The key's plan does not include something the cart needs: address lookups (v60 107), international rates (112), or product rate rules for a line's taxabilityCode (113). With details and itemId.

- `error` (string, required) — What went wrong. For a failed rate lookup it comes from the same mapping /merchant/cart/calculate uses; branch on it and the HTTP status.
- `details` (string, optional) — For a failed rate lookup, the message this endpoint has always published for the v60 code behind it. Kept for integrations that match on it, but for several codes it names an unrelated cause. For a request that does not bind or a bad transactionDate, why it was rejected.
- `itemId` (string, optional) — The line item a failed rate lookup failed on: the first line when the cart's own sourcing failed.
- `message` (string, optional) — Sent by the endpoint's per-minute rate limit and by an account store failure.
- `status` (string, optional) — The account status, on a 401 for an invalid or inactive key.

### 422 Calculate Cart Request Unprocessable Entity Error

The destination address could not be resolved (v60 102, 103, 104, 109, 110, or a result with no state): `destination address could not be resolved and a cart cannot be quoted without it`, with details and itemId. The cart is never quoted at its origin instead.

- `error` (string, required) — What went wrong. For a failed rate lookup it comes from the same mapping /merchant/cart/calculate uses; branch on it and the HTTP status.
- `details` (string, optional) — For a failed rate lookup, the message this endpoint has always published for the v60 code behind it. Kept for integrations that match on it, but for several codes it names an unrelated cause. For a request that does not bind or a bad transactionDate, why it was rejected.
- `itemId` (string, optional) — The line item a failed rate lookup failed on: the first line when the cart's own sourcing failed.
- `message` (string, optional) — Sent by the endpoint's per-minute rate limit and by an account store failure.
- `status` (string, optional) — The account status, on a 401 for an invalid or inactive key.

### 429 Calculate Cart Request Too Many Requests Error

More than 100 requests a minute to the endpoint, across all callers, on one server instance (`Rate limit exceeded`, with message), or the account's usage quota is used up (v60 108, `Rate limit exceeded`, with details and itemId).

- `error` (string, required) — What went wrong. For a failed rate lookup it comes from the same mapping /merchant/cart/calculate uses; branch on it and the HTTP status.
- `details` (string, optional) — For a failed rate lookup, the message this endpoint has always published for the v60 code behind it. Kept for integrations that match on it, but for several codes it names an unrelated cause. For a request that does not bind or a bad transactionDate, why it was rejected.
- `itemId` (string, optional) — The line item a failed rate lookup failed on: the first line when the cart's own sourcing failed.
- `message` (string, optional) — Sent by the endpoint's per-minute rate limit and by an account store failure.
- `status` (string, optional) — The account status, on a 401 for an invalid or inactive key.

### 500 Calculate Cart Request Internal Server Error

The rate engine failed (v60 106 or a code the mapping does not know, or no response), a rate response carried no SALES_TAX total or an unusable rate (`Unknown API error`), or the API key could not be checked (`Account lookup failed`, with message).

- `error` (string, required) — What went wrong. For a failed rate lookup it comes from the same mapping /merchant/cart/calculate uses; branch on it and the HTTP status.
- `details` (string, optional) — For a failed rate lookup, the message this endpoint has always published for the v60 code behind it. Kept for integrations that match on it, but for several codes it names an unrelated cause. For a request that does not bind or a bad transactionDate, why it was rejected.
- `itemId` (string, optional) — The line item a failed rate lookup failed on: the first line when the cart's own sourcing failed.
- `message` (string, optional) — Sent by the endpoint's per-minute rate limit and by an account store failure.
- `status` (string, optional) — The account status, on a 401 for an invalid or inactive key.

## Types

### CartInput

- `currency` (CartCurrencyInput, required) — The currency the line-item prices are denominated in.
- `customerId` (string, required) — Your identifier for the customer. Echoed in the response.
- `destination` (CartAddressInput, required) — The ship-to address. It is always resolved, and a cart whose destination cannot be resolved fails with 422.
- `lineItems` (list of CartLineItemInput, required) — The line items, 1 to 250.
- `origin` (CartAddressInput, required) — The ship-from address. It is resolved only when the cart or a line is sourced at the origin, or for a prepared-food line, whose seller-located meal tax comes from it, unless the destination's meal tax already rules meal tax out. On a destination-sourced cart such a line resolves it to learn its state.

### CartOutput

- `cartId` (string, required) — A UUID generated for this calculation. Nothing is stored under it.
- `customerId` (string, required) — The customerId, as sent.
- `destination` (CartAddressInput, required) — The destination, as sent.
- `lineItems` (list of CartLineItemOutput, required) — The line items in the order sent, each with its tax.
- `origin` (CartAddressInput, required) — The origin, as sent.

### CartCurrencyInput

- `currencyCode` ("USD", required) — ISO 4217 currency code of the line-item prices. Only USD is accepted.

### CartAddressInput

- `address` (string, required) — The full street address as one string: street, city, state and ZIP. It is geocoded to the door, so it needs the account's address lookups (geo) entitlement.

### CartLineItemInput

- `itemId` (string, required) — Your identifier for the line item. A failed rate lookup names the line it failed on by this id.
- `price` (double, required) — Unit price. Tax is calculated on price x quantity.
- `quantity` (double, required) — Quantity. Fractional quantities are allowed.
- `taxabilityCode` (long, optional) — Taxability Information Code (TIC). Omit it, or send 0, for general goods, quoted at the address's SALES_TAX total. 10001 (shipping) and 11000 (handling) follow the address's shipping taxability. Any other code is a product code, which needs the product_rates entitlement: the line is quoted with the code's product rate rules and, for prepared food (41000, 41041), its meal tax. null is the same as omitting it.

### CartLineItemOutput

- `itemId` (string, required) — The line item's itemId, as sent.
- `price` (double, required) — The unit price, as sent.
- `quantity` (double, required) — The quantity, as sent.
- `tax` (CartTaxOutput, required) — The tax the line collects.

### CartTaxOutput

- `amount` (double, required) — The tax on the line, rounded to five decimal places.
- `rate` (double, required) — The total rate collected on the line, tax / (price x quantity), after the code's product rate rules or meal tax, rounded to five decimal places. 0 for an exempt line.

## Examples

**Request**

```json
{
  "items": [
    {
      "currency": {
        "currencyCode": "USD"
      },
      "customerId": "CUST-001",
      "destination": {
        "address": "1100 Congress Ave, Austin, TX 78701"
      },
      "lineItems": [
        {
          "itemId": "SKU-001",
          "price": 20,
          "quantity": 1
        }
      ],
      "origin": {
        "address": "1100 Congress Ave, Austin, TX 78701"
      }
    }
  ]
}
```

**Response**

```json
{
  "items": [
    {
      "cartId": "0b6c9f6e-3f6d-4b8e-9f7b-1c2d3e4f5a6b",
      "customerId": "CUST-001",
      "destination": {
        "address": "1100 Congress Ave, Austin, TX 78701"
      },
      "lineItems": [
        {
          "itemId": "SKU-001",
          "price": 20,
          "quantity": 1,
          "tax": {
            "amount": 1.65,
            "rate": 0.0825
          }
        }
      ],
      "origin": {
        "address": "1100 Congress Ave, Austin, TX 78701"
      }
    }
  ],
  "transactionDate": "2026-10-13T14:00:00Z"
}
```

**SDK Code**

```python
import requests

url = "https://api.zip-tax.com/calculate/cart"

payload = { "items": [
        {
            "currency": { "currencyCode": "USD" },
            "customerId": "CUST-001",
            "destination": { "address": "1100 Congress Ave, Austin, TX 78701" },
            "lineItems": [
                {
                    "itemId": "SKU-001",
                    "price": 20,
                    "quantity": 1
                }
            ],
            "origin": { "address": "1100 Congress Ave, Austin, TX 78701" }
        }
    ] }
headers = {
    "X-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.zip-tax.com/calculate/cart';
const options = {
  method: 'POST',
  headers: {'X-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"items":[{"currency":{"currencyCode":"USD"},"customerId":"CUST-001","destination":{"address":"1100 Congress Ave, Austin, TX 78701"},"lineItems":[{"itemId":"SKU-001","price":20,"quantity":1}],"origin":{"address":"1100 Congress Ave, Austin, TX 78701"}}]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.zip-tax.com/calculate/cart"

	payload := strings.NewReader("{\n  \"items\": [\n    {\n      \"currency\": {\n        \"currencyCode\": \"USD\"\n      },\n      \"customerId\": \"CUST-001\",\n      \"destination\": {\n        \"address\": \"1100 Congress Ave, Austin, TX 78701\"\n      },\n      \"lineItems\": [\n        {\n          \"itemId\": \"SKU-001\",\n          \"price\": 20,\n          \"quantity\": 1\n        }\n      ],\n      \"origin\": {\n        \"address\": \"1100 Congress Ave, Austin, TX 78701\"\n      }\n    }\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.zip-tax.com/calculate/cart")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"items\": [\n    {\n      \"currency\": {\n        \"currencyCode\": \"USD\"\n      },\n      \"customerId\": \"CUST-001\",\n      \"destination\": {\n        \"address\": \"1100 Congress Ave, Austin, TX 78701\"\n      },\n      \"lineItems\": [\n        {\n          \"itemId\": \"SKU-001\",\n          \"price\": 20,\n          \"quantity\": 1\n        }\n      ],\n      \"origin\": {\n        \"address\": \"1100 Congress Ave, Austin, TX 78701\"\n      }\n    }\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.zip-tax.com/calculate/cart")
  .header("X-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"items\": [\n    {\n      \"currency\": {\n        \"currencyCode\": \"USD\"\n      },\n      \"customerId\": \"CUST-001\",\n      \"destination\": {\n        \"address\": \"1100 Congress Ave, Austin, TX 78701\"\n      },\n      \"lineItems\": [\n        {\n          \"itemId\": \"SKU-001\",\n          \"price\": 20,\n          \"quantity\": 1\n        }\n      ],\n      \"origin\": {\n        \"address\": \"1100 Congress Ave, Austin, TX 78701\"\n      }\n    }\n  ]\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.zip-tax.com/calculate/cart', [
  'body' => '{
  "items": [
    {
      "currency": {
        "currencyCode": "USD"
      },
      "customerId": "CUST-001",
      "destination": {
        "address": "1100 Congress Ave, Austin, TX 78701"
      },
      "lineItems": [
        {
          "itemId": "SKU-001",
          "price": 20,
          "quantity": 1
        }
      ],
      "origin": {
        "address": "1100 Congress Ave, Austin, TX 78701"
      }
    }
  ]
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.zip-tax.com/calculate/cart");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"items\": [\n    {\n      \"currency\": {\n        \"currencyCode\": \"USD\"\n      },\n      \"customerId\": \"CUST-001\",\n      \"destination\": {\n        \"address\": \"1100 Congress Ave, Austin, TX 78701\"\n      },\n      \"lineItems\": [\n        {\n          \"itemId\": \"SKU-001\",\n          \"price\": 20,\n          \"quantity\": 1\n        }\n      ],\n      \"origin\": {\n        \"address\": \"1100 Congress Ave, Austin, TX 78701\"\n      }\n    }\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = ["items": [
    [
      "currency": ["currencyCode": "USD"],
      "customerId": "CUST-001",
      "destination": ["address": "1100 Congress Ave, Austin, TX 78701"],
      "lineItems": [
        [
          "itemId": "SKU-001",
          "price": 20,
          "quantity": 1
        ]
      ],
      "origin": ["address": "1100 Congress Ave, Austin, TX 78701"]
    ]
  ]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.zip-tax.com/calculate/cart")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```