Skip to content

Python SDK — Changelog

Backwards-compatibility policy: pre-1.0 (0.x) releases may include breaking changes as the client tracks the API contract. From v0.1.0 onward, changes within a minor version are additive — new fields are added with extra="allow", new methods are appended, and deprecations are flagged at least one minor version before removal.

Additive. Surfaces the two new meta fields the API added on 2026-08-14 (GET /v1/tax, simple mode). Backward-compatible — new optional fields only.

  • Meta.effective_date — the served rate’s legal-effect date (ISO YYYY-MM-DD), or None. Populated only where the source Department of Revenue publishes a genuine per-row effective date; None for the ~26 of 64 jurisdictions whose source publishes an edition or no per-rate date.
  • Meta.effective_date_basis — always present: publisher_effective_date | edition_only | no_published_date. Describes the provenance of effective_date, not a legal characterization. Modelled as an open str (not a closed enum), so an unrecognised future value passes through unchanged rather than raising.
  • meta.period is deprecated in the docstring (it conflated a legal-effect date and a publication-quarter edition and is None for most states); its wire value is unchanged. Prefer effective_date + effective_date_basis, or GET /v1/freshness source_period for the edition.

Breaking, and deliberate. Aligns the Python client field-for-field and error-type-for-error-type with the PHP and Node SDKs, verified against live responses. Shipped as one break (not a 0.1.2 then a 0.2.0) because adoption is still effectively zero — cheaper to break now than after real users arrive.

  • Rate fields are now the API’s wire STRINGS (rate.combined_rate == "0.08250"), not floats. The API types every rate as a fixed 5-decimal string specifically so binary floating point never touches tax arithmetic; 0.1.x undid that at the boundary. Convert explicitly: rate_to_float(...) (new export), the Rate.combined_rate_as_float() / state_rate_as_float() / rate_as_float(key) accessors, or response.combined_rate (a float accessor). response.combined_rate still returns a float; new response.combined_rate_string returns the string. Absent buckets are "0.00000" (never None).
  • Exception hierarchy aligned. New BillingError (402), TierError (403), NotAvailableError (501), TransportError (network/DNS/TLS/timeout). Mapping is now driven by the machine error.code first, then HTTP status — so a proxy that rewrites the status can’t defeat it. PaymentRequiredError / ForbiddenError remain as aliases of BillingError / TierError, so except PaymentRequiredError still works.
  • Transport failures now raise TransportError (was a generic timeout TaxQLError).
  • Structured-address fix: when a city is supplied, the street line is sent on street (not the single-line address parser, which rejects a bare street).
  • resolved_via docstring neutralized (removed a pre-declassification internal name); captured fixtures refreshed.

First published release (pip install taxql). Aligns the client with the API’s default best-rate response (mode=simple): a single applicable rate rather than a candidate-row array. Breaking relative to the never-published v0.0.1.

  • TaxResponse is now { rate: Rate, meta: Meta }. response.combined_rate returns the single best rate (no exception); response.confidence and response.warnings read from meta; the per-level breakdown and resolved place/county live on response.rate.
  • Removed: TaxRow, Components, ResolvedPlace, PlaceComponent, BillingInfo, and AmbiguousLookupError (the multi-row model). For the verbose per-jurisdiction body, request mode=full on the raw HTTP API and read response.model_dump().
  • warnings items may be plain strings or structured objects with a code (e.g. place_input_mismatch).
  • Rate.country_rate added — the national-level rate field the wire always carries (0 for the US).
  • Errors: typed PaymentRequiredError (402) and ForbiddenError (403); the machine error_code and support_reference are surfaced on every exception from the { "error": {…} } envelope.

First release. Sync (TaxQL) + async (AsyncTaxQL) clients, typed response models, error hierarchy, retries with exponential backoff (honoring Retry-After on 429), forward-compatible models (extra="allow").

Public API:

  • TaxQL(api_key, *, base_url, timeout, max_retries, http_client)
  • client.lookup(state, *, address, city, zip, location, lat, lng, as_of, period) -> TaxResponse
  • client.health() -> HealthResponse
  • AsyncTaxQL mirror of the above
  • Response models: TaxResponse, TaxRow, Components, ResolvedPlace, PlaceComponent, BillingInfo, HealthResponse
  • Error classes: TaxQLError, AuthError, RateLimitError, NotFoundError, ValidationError, ServiceError, AmbiguousLookupError

Convenience properties on TaxResponse:

  • .combined_rate — single-row rate; raises AmbiguousLookupError for multi-row responses
  • .primary_row — single-row access with the same semantics
  • .resolved_place — single-row resolved-place metadata or None

Known gaps (planned for v0.1.0):

  • CLI tool (taxql lookup tx --address ...)
  • Custom retry policies (jitter, per-status-code backoff)
  • Streaming / batch lookup methods (not exposed by the API yet)
  • Telemetry hooks (deferred until customer signal)