Skip to main content
The calculation object contains tax rate information with support for per-unit line item pricing, IP-based tax resolution, origin addresses, metadata passthrough, and enhanced tax jurisdiction detail. A calculation also includes a calculation_id that you’ll use to record a transaction for filing.

When to create Calculations

Calculations should be used any time you need to calculate sales tax before charging a customer for a transaction. Most e-commerce clients will submit a POST to /tax/calculations during their checkout flow after the end customer has submitted their address, but before collecting payment. core flow

How to create Calculations

curl --request POST \
  --url https://api.numeralhq.com/tax/calculations \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --header 'X-API-Version: 2026-03-01' \
  --data '{
  "customer": {
    "address": {
      "address_city": "Nashville",
      "address_country": "US",
      "address_line_1": "123 Broadway",
      "address_postal_code": "37203",
      "address_province": "TN",
      "address_type": "billing"
    }
  },
  "metadata": {
    "payment_id": "68407cb7-e1fa-47fa-8244-bd9411d53c61"
  },
  "order_details": {
    "automatic_tax": "auto",
    "customer_currency_code": "USD",
    "line_items": [
      {
        "amount": 1000000,
        "product_category": "BEVERAGES",
        "quantity": 1
      },
      {
        "amount": 1000000,
        "product_category": "GENERAL_MERCHANDISE",
        "quantity": 1
      }
    ],
    "tax_included_in_amount": false
  },
  "origin_address": {
    "address_city": "Nashville",
    "address_country": "US",
    "address_line_1": "500 Church St",
    "address_line_2": "",
    "address_postal_code": "37219",
    "address_province": "TN"
  }
}'
{
  "testmode": true,
  "id": "calc_17725956342644181bdd4-5975-44d5-8c96-05233f565fdd",
  "object": "tax.calculation",
  "customer_currency_code": "USD",
  "line_items": [
    {
      "line_item_id": "li_1772595634232f1d34578-aed2-42cb-a917-09b2c8322b20",
      "product": {
        "reference_line_item_id": "",
        "reference_product_id": "default-beverages",
        "reference_product_name": "Default BEVERAGES Product",
        "product_tax_code": "BEVERAGES"
      },
      "tax_jurisdictions": [
        {
          "tax_rate": 0.04,
          "tax_due_decimal": 40000,
          "fee_amount": 0,
          "rate_type": "FOOD_AND_DRUG STATE SALES TAX",
          "tax_authority_name": "Tennessee",
          "tax_authority_type": "",
          "tax_type": "SALES"
        },
        {
          "tax_rate": 0.0225,
          "tax_due_decimal": 3600,
          "fee_amount": 0,
          "rate_type": "FOOD_AND_DRUG CITY LOCAL SALES TAX",
          "tax_authority_name": "Place 52006, TN",
          "tax_authority_type": "",
          "tax_type": "SALES"
        },
        {
          "tax_rate": 0.005,
          "tax_due_decimal": 800,
          "fee_amount": 0,
          "rate_type": "FOOD_AND_DRUG DISTRICT LOCAL SALES TAX",
          "tax_authority_name": "Other Special Applications 91951",
          "tax_authority_type": "",
          "tax_type": "SALES"
        }
      ],
      "tax_amount": 44400,
      "amount_excluding_tax": 1000000,
      "amount_including_tax": 1044400,
      "quantity": 1
    },
    {
      "line_item_id": "li_177259563426441045f22-c55e-4a4b-a1b9-e880b1d6a7b0",
      "product": {
        "reference_line_item_id": "",
        "reference_product_id": "default-general-merchandise",
        "reference_product_name": "Default GENERAL_MERCHANDISE Product",
        "product_tax_code": "GENERAL_MERCHANDISE"
      },
      "tax_jurisdictions": [
        {
          "tax_rate": 0.0275,
          "tax_due_decimal": 4400,
          "fee_amount": 0,
          "rate_type": "SINGLE_ARTICLE STATE SALES TAX",
          "tax_authority_name": "Tennessee",
          "tax_authority_type": "",
          "tax_type": "SALES"
        },
        {
          "tax_rate": 0.07,
          "tax_due_decimal": 70000,
          "fee_amount": 0,
          "rate_type": "GENERAL STATE SALES TAX",
          "tax_authority_name": "Tennessee",
          "tax_authority_type": "",
          "tax_type": "SALES"
        },
        {
          "tax_rate": 0.0225,
          "tax_due_decimal": 3600,
          "fee_amount": 0,
          "rate_type": "GENERAL CITY LOCAL SALES TAX",
          "tax_authority_name": "Place 52006, TN",
          "tax_authority_type": "",
          "tax_type": "SALES"
        },
        {
          "tax_rate": 0.005,
          "tax_due_decimal": 800,
          "fee_amount": 0,
          "rate_type": "GENERAL DISTRICT LOCAL SALES TAX",
          "tax_authority_name": "Other Special Applications 91951",
          "tax_authority_type": "",
          "tax_type": "SALES"
        }
      ],
      "tax_amount": 78800,
      "amount_excluding_tax": 1000000,
      "amount_including_tax": 1078800,
      "quantity": 1
    }
  ],
  "total_tax_amount": 123200,
  "tax_included_in_amount": false,
  "total_amount_excluding_tax": 2000000,
  "total_amount_including_tax": 2123200,
  "expires_at": 1772682034,
  "metadata": {
    "payment_id": "68407cb7-e1fa-47fa-8244-bd9411d53c61"
  },
  "customer": {
    "type": "CONSUMER"
  },
  "automatic_tax": "auto",
  "address_resolution_status": "EXACT",
  "address_used": {
    "address_line_1": "500 Church St",
    "address_line_2": "",
    "address_city": "Nashville",
    "address_province": "TN",
    "address_postal_code": "37219",
    "address_country": "US"
  }
}
We return both the aggregate tax information as well as a detailed breakdown for each line item. Many users will just use the total_tax_amount to identify what to charge a user, but you will always have the details as you need them. For IP-based calculations, see the IP-Based Tax Resolution section below.
Breaking Change (from 2026-01-01): The amount field on line items represents the per-unit price. The taxable base is calculated as amount x quantity. This is a change from versions prior to 2026-01-01 where amount represented the total line item value.

Request Parameters

Customer Object

{
  "customer": {
    "id": "cus_123456789", // Optional customer ID
    "address": {
      "address_line_1": "123 Broadway",
      "address_line_2": "", // Optional
      "address_city": "Nashville",
      "address_province": "TN",
      "address_postal_code": "37203",
      "address_country": "US",
      "address_type": "billing" // "shipping" or "billing"
    },
    "type": "CONSUMER", // "BUSINESS" or "CONSUMER" (default if omitted)
    "tax_ids": [ // Required for BUSINESS (except US), not allowed for CONSUMER
      {
        "type": "eu_vat", // Stripe-style tax ID type (439+ types supported)
        "value": "DE123456789"
      }
    ]
  }
}
  • address (object): Optional if customer.ip is provided. At least one of address or ip must be present.
  • ip (object): New in 2026-03-01. See IP-Based Tax Resolution below.
  • type (string): Customer type affecting tax calculation.
    • CONSUMER (default) - Individual consumer, standard B2C tax calculations
    • BUSINESS - Business entity requiring tax IDs for B2B tax logic
  • tax_ids (array): Required for BUSINESS customers (except US), not allowed for CONSUMER customers.
    • type (string): Stripe-style tax ID type. 439+ types supported including us_ein, eu_vat, gb_vat, au_abn, ca_bn, jp_cn, and many more.
    • value (string): Valid tax identification number

Origin Address

{
  "origin_address": {
    "address_line_1": "500 Church St",
    "address_line_2": "", // Optional
    "address_city": "Nashville",
    "address_province": "TN",
    "address_postal_code": "37219",
    "address_country": "US"
  }
}
The ship-from address used for origin-based tax jurisdictions. Required for accurate tax calculation in states that use origin-based sourcing rules.

Order Details

{
  "order_details": {
    "customer_currency_code": "USD", // Supports 32 currencies
    "tax_included_in_amount": false,
    "automatic_tax": "auto", // "auto" or "disabled"
    "line_items": [
      {
        "reference_line_item_id": "line_123456789", // Optional
        "reference_product_id": "p-1233543", // Required if no product_category
        "product_category": "GENERAL_MERCHANDISE", // Required if no reference_product_id
        "amount": 1000000, // Per-unit price in smallest currency unit (e.g., cents)
        "quantity": 1 // Taxable base = amount x quantity
      }
    ]
  }
}
  • amount (number): Per-unit price in the currency’s smallest unit (e.g., cents for USD). The taxable base is amount x quantity.
  • automatic_tax (string): Controls tax collection and registration behavior.
    • auto - Return tax rates everywhere you have an active registration
    • disabled - Always return 0 tax regardless of thresholds

Metadata

{
  "metadata": {
    "payment_id": "68407cb7-e1fa-47fa-8244-bd9411d53c61"
  }
}
Optional. Store arbitrary key-value pairs for your own reference. Values must be strings of 255 characters or fewer. Metadata is echoed back in the response unchanged.

Features from 2026-01-01

Per-Unit Line Item Pricing

The amount field on line items now represents the per-unit price rather than the total line item value. The taxable base is calculated as amount x quantity.
Versionamount MeaningExample: 3 items at $25.00 each
2025-05-12 and earlierTotal line item value"amount": 7500, "quantity": 3
2026-01-01+Per-unit price"amount": 2500, "quantity": 3

Enhanced Tax Jurisdiction Detail

Tax jurisdiction objects in the response now include richer detail from the tax engine:
FieldTypeDescription
tax_due_decimalnumberTax amount due for this jurisdiction in the currency’s smallest unit
tax_authority_namestringName of the tax authority (e.g., “Tennessee”, “ALLEGHENY”)
tax_authority_typestringType of authority (e.g., “STATE”, “COUNTY”, “CITY”, “DISTRICT”)
tax_typestringType of tax: SALES, USE, VAT, or GST
rate_typestringDescriptive rate classification (e.g., “GENERAL STATE SALES TAX”, “FOOD_AND_DRUG CITY LOCAL SALES TAX”)
The jurisdiction_name and note fields from prior versions are no longer included in 2026-01-01+ responses.

Origin Address Support

You can now provide an origin_address representing the ship-from location. This is used for origin-based tax jurisdictions where the seller’s location affects the applicable tax rate.

Address Resolution

Responses include information about how the address was resolved:
  • address_resolution_status (string): Indicates the precision of the address match.
ValueDescription
EXACTExact street-level address match
POSTAL_FALLBACK_1Fell back to postal code-level match
POSTAL_ONLYOnly postal code was used for resolution
  • address_used (object): The resolved address that was actually used for the tax calculation.

Expanded Tax ID Types

Tax ID types have been expanded from 2 types (VAT, GST) to 439+ Stripe-style types. Examples include us_ein, eu_vat, gb_vat, au_abn, ca_bn, jp_cn, in_gst, br_cnpj, and many more.

Standard Error Format

Error responses use a flat format:
{
  "code": 400,
  "type": "PRODUCT_NOT_FOUND",
  "message": "Product not found"
}
This replaces the nested format ({ "error": { "code": "...", "message": "..." } }) from versions prior to 2026-01-01.

IP-Based Tax Resolution (New in 2026-03-01)

Starting with API version 2026-03-01, you can provide a customer IP address instead of a full street address. The API will geo-resolve the IP to determine the taxable location.

The customer.ip Object

{
  "customer": {
    "type": "CONSUMER",
    "ip": {
      "value": "217.217.113.167",   // IPv4 or IPv6
      "resolution": "strict"         // "strict", "zero", "approximate", or "best_effort"
    }
  }
}
At least one of customer.address or customer.ip must be provided. If both are given, the address is used first with IP as a fallback.

Resolution Modes

ModeBehavior
strict (default)Error if IP cannot resolve to sufficient detail for tax calculation
zeroReturn zero-rate response if resolution is insufficient
approximateAttempts to resolve the IP to an address using heuristics
best_effortTry approximate first, fall back to zero rates
Numeral strongly recommends using strict mode to stay as compliant as possible.

New Response Fields

Responses include additional fields when using this API version:
FieldTypeDescription
location_sourcestring"address" or "ip" — which input was used for tax determination
resolution_precisionstringPrecision level: STREET, POSTAL_PLUS, POSTAL, PROVINCE, COUNTRY, or APPROXIMATED
address_usedobjectThe resolved address that was actually used for tax calculation
curl --request POST \
  --url https://api.numeralhq.com/tax/calculations \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --header 'X-API-Version: 2026-03-01' \
  --data '{
  "customer": {
    "type": "CONSUMER",
    "ip": {
      "value": "217.217.113.167",
      "resolution": "strict"
    }
  },
  "order_details": {
    "customer_currency_code": "USD",
    "tax_included_in_amount": false,
    "automatic_tax": "auto",
    "line_items": [
      {
        "product_category": "SAAS_GENERAL",
        "reference_line_item_id": "ip-test-001",
        "amount": 10000,
        "quantity": 5
      }
    ]
  }
}'
{
  "testmode": true,
  "id": "calc_17725714816806a81b828-0757-4ec2-9307-3c1001733f73",
  "object": "tax.calculation",
  "customer_currency_code": "USD",
  "line_items": [
    {
      "line_item_id": "li_17725714816804066f7e8-06ad-4286-9070-ad90991246d0",
      "product": {
        "reference_line_item_id": "ip-test-001",
        "reference_product_id": "default-saas-general",
        "reference_product_name": "Default SAAS_GENERAL Product",
        "product_tax_code": "SAAS_GENERAL"
      },
      "tax_jurisdictions": [
        {
          "tax_rate": 0.06,
          "tax_due_decimal": 3000,
          "fee_amount": 0,
          "rate_type": "GENERAL STATE SALES TAX",
          "tax_authority_name": "Pennsylvania",
          "tax_authority_type": "",
          "tax_type": "SALES"
        },
        {
          "tax_rate": 0.01,
          "tax_due_decimal": 500,
          "fee_amount": 0,
          "rate_type": "GENERAL COUNTY LOCAL SALES TAX",
          "tax_authority_name": "ALLEGHENY",
          "tax_authority_type": "",
          "tax_type": "SALES"
        }
      ],
      "tax_amount": 3500,
      "amount_excluding_tax": 50000,
      "amount_including_tax": 53500,
      "quantity": 5
    }
  ],
  "total_tax_amount": 3500,
  "tax_included_in_amount": false,
  "total_amount_excluding_tax": 50000,
  "total_amount_including_tax": 53500,
  "expires_at": 1772657881,
  "customer": {
    "type": "CONSUMER"
  },
  "automatic_tax": "auto",
  "address_resolution_status": "POSTAL_ONLY",
  "address_used": {
    "address_line_1": "",
    "address_line_2": "",
    "address_city": "",
    "address_province": "PA",
    "address_postal_code": "15212",
    "address_country": "US"
  },
  "location_source": "ip",
  "resolution_precision": "POSTAL"
}

IP Resolution Errors

When using strict mode, the API returns a 422 error if the IP cannot be resolved to sufficient detail for tax calculation.
curl --request POST \
  --url https://api.numeralhq.com/tax/calculations \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --header 'X-API-Version: 2026-03-01' \
  --data '{
  "customer": {
    "type": "CONSUMER",
    "ip": {
      "value": "2600:387:2:803::81",
      "resolution": "strict"
    }
  },
  "order_details": {
    "customer_currency_code": "USD",
    "tax_included_in_amount": false,
    "automatic_tax": "auto",
    "line_items": [
      {
        "product_category": "SAAS_GENERAL",
        "amount": 10000,
        "quantity": 1
      }
    ]
  }
}'
{
  "code": 422,
  "type": "IP_RESOLUTION_INSUFFICIENT_US",
  "message": "IP address resolved to US-GA but could not determine a postal code. Please provide customer.address with address_postal_code and address_province."
}
Error TypeHTTP CodeDescription
INVALID_IP_FORMAT400ip.value is not a valid IPv4 or IPv6 address
IP_RESOLUTION_FAILED422IP could not be resolved to any country
IP_RESOLUTION_INSUFFICIENT_US422IP resolved to a US state but no postal code could be determined
IP_RESOLUTION_INSUFFICIENT_CA422IP resolved to Canada but no province could be determined
To avoid resolution errors, use resolution: "best_effort" or resolution: "zero" instead of strict. These modes will return a zero-rate response when resolution is insufficient rather than an error.

Further documentation

The full documentation for creating calculations is on this page.