Skip to content

ExciseIQ Extension (Deprecated)

Decommissioned 2026-04-23

ext_exciseiq was retired by Decided #225. New installs use ext_excise_engine — see Building Extensions → Excise Tax for the current integration. The notes below are retained for operators still on a pre-2026.05 build during their migration window.

Extension name: ext-exciseiq (legacy) Strategy: TaxCalculationStrategy (name: exciseiq) Spec: Decided Tax #1, Decided #90, Decided #103, superseded by Decided #225

ExciseIQ provided excise tax calculation for tobacco, vape, and nicotine products with county-level jurisdiction precision. It ran alongside the built-in sales tax strategy via resolve_all. The replacement engine retains the same product.traits contract — see the migration note below.

Architecture

ExciseIQTaxStrategy
├── API path: ExciseIQAPIClient → ExciseIQ SaaS
└── Fallback path: LocalFallbackEngine → exciseiq_tax_* tables
                              SyncExciseIQDataWorkflow (Temporal, daily)

Files

File Purpose
extension.py Entry point, manifest, config_schema, strategy registration
strategy.py ExciseIQTaxStrategy — routes to API or fallback
api.py Async httpx client for ExciseIQ SaaS (Basic auth, rate limiting)
fallback.py LocalFallbackEngine — 7 rate types, jurisdiction cascade
models.py SQLAlchemy models for local cache tables
sync.py Temporal activity for syncing data from ExciseIQ API
resolvers.py GraphQL queries and mutations for admin

API Client

The ExciseIQAPIClient communicates with the ExciseIQ SaaS API:

  • Auth: HTTP Basic (API key as username, empty password)
  • Base URL: https://api.exciseiqapi.com (configurable)
  • Rate limiting: Reads X-RateLimit-* headers; backs off on 429

Endpoints

Method Path Purpose
POST /api/v1/tax/excise-tax-calculation Live excise tax calculation
GET /api/v1/tax/tax-classes Sync tax classes
GET /api/v1/tax/tax-regions Sync regions
GET /api/v1/tax/tax-rates Sync rates
GET /api/v1/location/counties County lookup

Rate Types

The fallback engine supports all ExciseIQ rate types:

Rate Type Formula
percentage_of_price line_total * rate
percentage_of_cost cost * qty * rate
flat_cost_per_item amount * qty
flat_cost_per_milliliters amount * ml * qty
flat_cost_per_mfg_weight_oz amount * weight * qty
flat_cost_per_order amount (once per order)
percentage_of_price_x_milliliters price * ml * rate * qty

Product Traits

ExciseIQ uses the product.traits JSONB column. Relevant keys:

Trait Key Type Purpose
exciseiq_tax_class string Tax class slug for rate matching
contains_nicotine "yes"/"no" Excise applicability flag
product_type string "disposable", "e_liquid", "tobacco"
milliliters string/number Product volume in ml
nic_percentage string/number Nicotine percentage
mfg_weight string/number Manufacturing weight in oz
product_cost string/number Product cost for cost-based rates

Variant traits override product traits when both are set.

Data Sync

Tax classes, regions, and rates sync from the ExciseIQ API via a Temporal scheduled workflow:

  • Workflow: SyncExciseIQDataWorkflow
  • Activity: sync_exciseiq_data
  • Schedule: vectis-sync-exciseiq-data (default: daily)
  • Manual trigger: exciseiq_trigger_sync GraphQL mutation

Configuration

Stored in ChannelExtension.config JSONB. Configured via the admin Extensions page.

Key Type Default Description
api_url string https://api.exciseiqapi.com API base URL
api_key string (required) API authentication key
calculate_tax_by enum shipping Address type for jurisdiction
fallback_on_api_failure boolean true Use local rates when API fails
enable_counties boolean false Show county in checkout
county_required boolean false Require county at checkout
sync_schedule enum daily Sync frequency
default_milliliters string 0 Fallback ml value
default_nic_percentage string 0 Fallback nicotine %
default_mfg_weight string 0 Fallback weight (oz)
default_cost string 0 Fallback product cost
debug_logging boolean false Verbose logging

GraphQL API

Queries

  • exciseiq_tax_classes(active_only) — synced tax classes
  • exciseiq_tax_regions(state, active_only) — synced regions
  • exciseiq_tax_rates(region_id, class_id, active_only) — synced rates
  • exciseiq_counties(state, search, limit) — county data
  • exciseiq_sync_logs(limit) — sync history

Mutations

  • exciseiq_test_connection — verify API credentials
  • exciseiq_trigger_sync — start manual data sync
  • exciseiq_calculate_tax(address, items) — admin tax calculator

Migration to ext_excise_engine

The replacement extension reads the same product.traits keys (exciseiq_tax_class, contains_nicotine, product_type, milliliters, nic_percentage, mfg_weight, product_cost) so existing catalog data continues to work without re-tagging. The one-click migration tool copies your ExciseIQ config (api_key, calculate_tax_by, default_* fallbacks) into the new extension's settings and disables the legacy strategy. The legacy seed data is no longer installed; uninstalling ext_exciseiq after migration is safe.