TaxLens API Docs

TaxLens calculates, stores, and audits accommodation taxes for hotel, OTA, and marketplace booking flows. Start with an in-app calculation, then wire the same request shape into your backend with an API key.

Base URL
https://api.taxlens.getdynamiq.ai
Content Type
application/json
API Version
v1
Auth
X-API-Key header

Get Started

The normal integration path is calculate first, persist once the booking is confirmed, then use booking endpoints for downstream operations.

For booking lifecycle work, jump directly to create booking, refund booking, or booking document.

Authentication

All API requests (except /health) require an API key passed in the X-API-Key header. New organizations can browse data and use the calculator immediately; production API keys are enabled after onboarding review.

Example
curl https://api.taxlens.getdynamiq.ai/v1/jurisdictions \
  -H "X-API-Key: <your-api-key>"

Requests without a valid key receive a 401 Unauthorized response.

Errors

The API uses standard HTTP status codes. Error responses include a JSON body with a detail field.

CodeMeaning
200Success
201Created (for POST endpoints)
400Bad request — invalid parameters
401Unauthorized — missing or invalid API key
404Not found — resource does not exist
409Conflict — resource already exists
422Validation error — check field constraints
500Internal server error
Error Response
{
  "detail": "Jurisdiction not found: XX-YY-ZZ"
}

Tax Calculation

POST/v1/tax/calculate

Calculate Tax

Calculate all applicable accommodation taxes for a specific stay. The engine traverses the jurisdiction hierarchy (country → state → city), collects all active rates, evaluates rules (exemptions, reductions, caps, surcharges), and returns an itemized breakdown. Supports flat, percentage, and tiered rate types with per-stay and per-night charging models.

Two expanded inputs are available for non-uniform stays: pass `nightly_rates` (list of { date, rate }) for variable-rate stays (e.g. promo-priced opening night, weekly-rate discounts) — it overrides `nightly_rate × nights`. Pass `guests` (list of GuestSpec) for mixed-guest stays where exemptions depend on per-guest details (e.g. one resident + one tourist) — it overrides the scalar `guest_*` fields and `number_of_guests`. You can also skip `jurisdiction_code` and provide `lat` + `lng` instead; the geocoder will resolve the matching jurisdiction.

Request Body

jurisdiction_codestringoptional

ISO jurisdiction code (e.g. "US-NY-NYC", "HR-19-DBV"). Optional if lat+lng provided.

latfloatoptional

Latitude. Provide with `lng` instead of `jurisdiction_code` to auto-resolve via geocoder.

lngfloatoptional

Longitude. Paired with `lat`.

stay_datedaterequired

Check-in date (YYYY-MM-DD).

checkout_datedateoptional

Check-out date. Optional, but when supplied it must equal `stay_date + nights`.

nightly_ratedecimalrequired

Room rate per night. Must be > 0. Overridden when `nightly_rates` is provided.

currencystringrequired

ISO 4217 currency code (e.g. "USD", "EUR", "JPY").

nightsintegerrequired

Number of nights (>= 1).

number_of_guestsintegeroptional

Guest count. Affects per-person taxes. Auto-derived from `guests.length` when present.

Default: 1

property_typestringoptional

Primary lodging product type. Inputs are normalized before rule matching; for example short_term_rental -> str, villa -> vacation_rental, and aparthotel -> apartment_hotel.

hotelmotelresortboutiquepalaceapartment_hotelhostelbnbstrvacation_rentalcampgroundapartmentlong_term_rental

Default: "hotel"

property_classificationstringoptional

Optional authority-assigned lodging class when property_type/star_rating are not precise enough, e.g. Dubai holiday_home_deluxe.

star_ratingintegeroptional

Hotel star rating (1-5). Used for tiered rates (e.g. Florence, Milan).

guest_typestringoptional

Guest classification: standard, business, government, etc.

Default: "standard"

guest_ageintegeroptional

Guest age. Triggers age-based exemptions/reductions (e.g. Croatia: under 12 exempt, 12-17 half price). Overridden when `guests` is provided.

guest_nationalitystringoptional

ISO country code. Triggers nationality-based rules (e.g. Bhutan: SAARC countries exempt). Overridden when `guests` is provided.

is_marketplacebooleanoptional

Whether booked through an OTA marketplace. Triggers platform surcharges (e.g. Denver +2%).

Default: false

platform_typestringoptional

Booking channel: direct, ota, metasearch, etc.

Default: "direct"

is_bundledbooleanoptional

Whether the room is part of a bundled package.

Default: false

property_room_countintegeroptional

Total rooms in the property (≥0). Drives operator-size tier rules (e.g. Croatia's per-bed tax varies by property size).

bedroom_countintegeroptional

Bedrooms in the booked unit (≥0). Used by per-bedroom cap rules.

payment_methodstringoptional

card, cash, bank_transfer, mobile_money. Some jurisdictions exempt cash transactions below a threshold.

Default: "unknown"

booking_purposestringoptional

business, leisure, government, military, diplomatic. Drives exempt-entity rules.

guestslist[GuestSpec]optional

Per-guest details. Overrides scalar `guest_age` / `guest_nationality` / `number_of_guests` when present. GuestSpec: { nationality?: string, age?: int (0-130), type?: string (canonical guest_type: "resident"|"non_resident"|"disabled"|"disabled_companion"|"diplomat"|"military"|"government"|"ngo"|"student"|"senior"|…), disability_percentage?: int (0-100) }. Legacy `residency_status` / `exempt_entity_type` are still accepted as input aliases for `type`.

nightly_rateslist[NightlyRateEntry]optional

Per-night rate schedule. Overrides `nightly_rate × nights` when present. NightlyRateEntry: { date: "YYYY-MM-DD", rate: decimal > 0 }. Useful for promo-priced opening nights or weekly-rate discounts.

line_itemslist[LineItem]optional

Additional itemized charges. Each item is taxed by rates matching its `item_type` to `tax_category.base_type`; for example, resort fees can be taxed separately from room revenue and cleaning fees can follow fee-specific VAT treatment.

descriptionstring
item_typeone of the values below
amountdecimal > 0, per night
nightsoptional integer; defaults to booking nights
taxableoptional boolean; defaults to true
roomresort_feecleaning_feeservice_feedestination_feesustainability_feeamenity_feecommissionother

Room line items are ignored. nightly_rate x nights remains the canonical room base.

price_includes_taxbooleanoptional

If true, the engine treats `nightly_rate` (and line_item amounts) as tax-inclusive and reverse-derives the base. Used by EU consumer-protection display compliance and Booking.com TaxPolicy Type=Inclusive.

Default: false

merchant_of_recordstringoptional

Who legally remits the tax. `property` = hotel/host (Booking agency, Vrbo non-MPF). `platform` = OTA/marketplace (Booking MoR, Airbnb MPF, ETG MoR). `none` or omitted = engine falls back to is_marketplace heuristic. Drives `collection_info.platform_collected_components` / `property_collected_components` in the response.

customer_typestringoptional

Customer profile: consumer | business | government | diplomat | ngo | exempt_entity. Drives B2B reverse-charge + government/diplomat exemptions. Defaults to consumer.

business_tax_idstringoptional

Business VAT ID / EIN / GSTIN. Truthy presence exposes `has_valid_vat_id` derived field on rule context (used for B2B reverse-charge gating).

settlement_currencystringoptional

ISO 4217 currency the platform will settle in (may differ from booking currency). Response includes `tax_breakdown.amount_in_settlement_currency` and `settlement_fx_rate`.

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/tax/calculate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-key>" \
  -d '{
    "jurisdiction_code": "ES-CT-BCN",
    "stay_date": "2026-06-15",
    "checkout_date": "2026-06-17",
    "nightly_rate": 180,
    "currency": "EUR",
    "nights": 2,
    "number_of_guests": 2,
    "property_type": "hotel",
    "star_rating": 4
  }'

# Compare another difficult jurisdiction with tax-on-tax:
curl -X POST https://api.taxlens.getdynamiq.ai/v1/tax/calculate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-key>" \
  -d '{
    "jurisdiction_code": "CA-ON-TOR",
    "stay_date": "2026-06-15",
    "nightly_rate": 220,
    "currency": "CAD",
    "nights": 2,
    "number_of_guests": 2,
    "property_type": "hotel"
  }'
Response
{
  "calculation_id": "calc_c484953024e7b4e04f7e9b05ba1ca866",
  "jurisdiction": {
    "code": "ES-CT-BCN",
    "name": "Barcelona",
    "path": "ES.CT.BCN"
  },
  "input": {
    "nightly_rate": 180,
    "nights": 2,
    "subtotal": 360,
    "currency": "EUR",
    "property_type": "hotel"
  },
  "tax_breakdown": {
    "components": [
      {
        "name": "VAT / Sales Tax (reduced rate)",
        "category_code": "vat_reduced",
        "jurisdiction_code": "ES",
        "jurisdiction_level": "country",
        "rate": 0.1,
        "rate_type": "percentage",
        "taxable_amount": "360",
        "tax_amount": "36.00",
        "legal_reference": "Ley 37/1992, de 28 de diciembre, del Impuesto sobre el Valor Anadido, Articulo 91.uno.2.2 - servicios de hosteleria al tipo reducido del 10%",
        "authority": "Agencia Tributaria",
        "line_item_index": null,
        "collected_by": null
      },
      {
        "name": "Tourism Tax (flat per person per night)",
        "category_code": "tourism_flat_person_night",
        "jurisdiction_code": "ES-CT",
        "jurisdiction_level": "state",
        "rate": 0.0,
        "rate_type": "tiered",
        "taxable_amount": null,
        "tax_amount": "7.20",
        "legal_reference": "Ley 2/2026 and ATC IEET tariff table, engine-compatible Catalonia regional IEET base from 2026-04-01. Barcelona municipality stacks its separate recargo in ES-CT-BCN tax_rate 5117.",
        "authority": "Agencia Tributaria de Catalunya",
        "line_item_index": null,
        "collected_by": null
      },
      {
        "name": "Tourism Tax (flat per person per night)",
        "category_code": "tourism_flat_person_night",
        "jurisdiction_code": "ES-CT-BCN",
        "jurisdiction_level": "city",
        "rate": 0.0,
        "rate_type": "tiered",
        "taxable_amount": null,
        "tax_amount": "26.40",
        "legal_reference": "Ley 2/2026 and ATC IEET tariff table, decomposed for TaxLens additive semantics: this ES-CT-BCN row contains only the Barcelona municipal recargo from 2026-04-01. The Catalan regional base remains in ES-CT tax_rate 3475.",
        "authority": "Ajuntament de Barcelona / Agencia Tributaria de Catalunya",
        "line_item_index": null,
        "collected_by": null
      }
    ],
    "total_tax": "69.60",
    "effective_rate": "0.1933333333333333333333333333",
    "currency": "EUR",
    "subtotal_excluding_tax": "360",
    "subtotal_including_tax": "429.60",
    "amount_in_settlement_currency": null,
    "settlement_currency": null,
    "settlement_fx_rate": null,
    "settlement_fx_error": null
  },
  "total_with_tax": "429.60",
  "rules_applied": [
    {
      "rule_id": 1460,
      "name": "Spanish IVA does not apply in Canary Islands (IGIC zone)",
      "rule_type": "exemption",
      "result": "skipped"
    },
    {
      "rule_id": 620,
      "name": "Catalonia IEET - capped at 7 nights",
      "rule_type": "cap",
      "result": "skipped"
    },
    {
      "rule_id": 627,
      "name": "Catalonia IEET - under-17 exemption (statewide)",
      "rule_type": "exemption",
      "result": "skipped"
    },
    {
      "rule_id": 621,
      "name": "Barcelona child exemption (under 17)",
      "rule_type": "exemption",
      "result": "skipped"
    },
    {
      "rule_id": 1363,
      "name": "Barcelona IEET - EU social-tourism program exemption",
      "rule_type": "exemption",
      "result": "skipped"
    },
    {
      "rule_id": 1362,
      "name": "Barcelona IEET - health/medical stay exemption",
      "rule_type": "exemption",
      "result": "skipped"
    },
    {
      "rule_id": 1361,
      "name": "Barcelona IEET - 7-night cap per continuous stay",
      "rule_type": "cap",
      "result": "applied"
    }
  ],
  "collection_info": {
    "who_collects": "property",
    "taxable_base": "room_rate",
    "platform_must_collect": false,
    "notes": [],
    "merchant_of_record": null,
    "platform_collected_components": [],
    "property_collected_components": [
      "vat_reduced",
      "tourism_flat_person_night",
      "tourism_flat_person_night"
    ]
  },
  "calculated_at": "2026-05-26T23:32:49.396031Z",
  "data_version": null
}
POST/v1/tax/calculate/batch

Batch Calculate

Calculate taxes for multiple stays in a single request. Each item in the batch is calculated independently. Useful for comparing rates across jurisdictions or processing bulk bookings.

Request Body

calculationsarrayrequired

Array of TaxCalculationRequest objects (same schema as single calculate endpoint).

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/tax/calculate/batch \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-key>" \
  -d '{
    "calculations": [
      {
        "jurisdiction_code": "ES-CT-BCN",
        "stay_date": "2026-06-15",
        "checkout_date": "2026-06-17",
        "nightly_rate": 180,
        "currency": "EUR",
        "nights": 2,
        "number_of_guests": 2,
        "property_type": "hotel",
        "star_rating": 4
      },
      {
        "jurisdiction_code": "CA-ON-TOR",
        "stay_date": "2026-06-15",
        "nightly_rate": 220,
        "currency": "CAD",
        "nights": 2,
        "number_of_guests": 2,
        "property_type": "hotel"
      }
    ]
  }'
Response
{
  "results": [
    {
      "id": "0",
      "total_tax": "69.60",
      "effective_rate": "0.1933333333333333333333333333",
      "components": [
        {
          "name": "VAT / Sales Tax (reduced rate)",
          "category_code": "vat_reduced",
          "jurisdiction_code": "ES",
          "jurisdiction_level": "country",
          "rate": 0.1,
          "rate_type": "percentage",
          "taxable_amount": "360",
          "tax_amount": "36.00",
          "legal_reference": "Ley 37/1992, Articulo 91.uno.2.2",
          "authority": "Agencia Tributaria",
          "line_item_index": null,
          "collected_by": null
        },
        {
          "name": "Tourism Tax (flat per person per night)",
          "category_code": "tourism_flat_person_night",
          "jurisdiction_code": "ES-CT",
          "jurisdiction_level": "state",
          "rate": 0.0,
          "rate_type": "tiered",
          "taxable_amount": null,
          "tax_amount": "7.20",
          "legal_reference": "Ley 2/2026 and ATC IEET tariff table",
          "authority": "Agencia Tributaria de Catalunya",
          "line_item_index": null,
          "collected_by": null
        },
        {
          "name": "Tourism Tax (flat per person per night)",
          "category_code": "tourism_flat_person_night",
          "jurisdiction_code": "ES-CT-BCN",
          "jurisdiction_level": "city",
          "rate": 0.0,
          "rate_type": "tiered",
          "taxable_amount": null,
          "tax_amount": "26.40",
          "legal_reference": "Ley 2/2026 and ATC IEET tariff table",
          "authority": "Ajuntament de Barcelona / Agencia Tributaria de Catalunya",
          "line_item_index": null,
          "collected_by": null
        }
      ],
      "error": null
    },
    {
      "id": "1",
      "total_tax": "99.46",
      "effective_rate": "0.2260454545454545454545454545",
      "components": [
        {
          "name": "Municipal Surcharge (%)",
          "category_code": "municipal_pct",
          "jurisdiction_code": "CA-ON-TOR",
          "jurisdiction_level": "city",
          "rate": 0.085,
          "rate_type": "percentage",
          "taxable_amount": "440",
          "tax_amount": "37.40",
          "legal_reference": "City of Toronto Bylaw 1259-2024",
          "authority": "City of Toronto",
          "line_item_index": null,
          "collected_by": null
        },
        {
          "name": "VAT / Sales Tax (standard)",
          "category_code": "vat_standard",
          "jurisdiction_code": "CA",
          "jurisdiction_level": "country",
          "rate": 0.05,
          "rate_type": "percentage",
          "taxable_amount": "477.40",
          "tax_amount": "23.87",
          "legal_reference": "Excise Tax Act, Part IX",
          "authority": "Canada Revenue Agency",
          "line_item_index": null,
          "collected_by": null
        },
        {
          "name": "VAT / Sales Tax (standard)",
          "category_code": "vat_standard",
          "jurisdiction_code": "CA-ON",
          "jurisdiction_level": "province",
          "rate": 0.08,
          "rate_type": "percentage",
          "taxable_amount": "477.40",
          "tax_amount": "38.19",
          "legal_reference": "Excise Tax Act, Part IX; Ontario HST provincial portion",
          "authority": "Ontario Ministry of Finance",
          "line_item_index": null,
          "collected_by": null
        }
      ],
      "error": null
    }
  ]
}

Jurisdictions

GET/v1/jurisdictions

List Jurisdictions

Retrieve a paginated list of jurisdictions. Supports filtering by country, type, status, and parent. Use this to discover available jurisdictions in the hierarchy.

Parameters

country_codestringoptional

Filter by ISO country code (e.g. "US", "HR")

jurisdiction_typestringoptional

Filter by type: country, state, province, region, city, district, special_zone

statusstringoptional

Filter by status: active, inactive, pending

parent_codestringoptional

Filter by parent jurisdiction code

qstringoptional

Search by name or code (case-insensitive)

limitintegeroptional

Max results (1-500)

Default: 100

offsetintegeroptional

Pagination offset

Default: 0

Request
curl https://api.taxlens.getdynamiq.ai/v1/jurisdictions?country_code=US&jurisdiction_type=state \
  -H "X-API-Key: <your-api-key>"
Response
[
  {
    "id": 2,
    "code": "US-NY",
    "name": "New York",
    "local_name": null,
    "jurisdiction_type": "state",
    "path": "US.NY",
    "parent_id": 1,
    "country_code": "US",
    "subdivision_code": "US-NY",
    "timezone": "America/New_York",
    "currency_code": "USD",
    "status": "active",
    "created_by": "system",
    "created_at": "2026-03-12T00:52:31.340Z",
    "updated_at": "2026-03-12T00:52:31.340Z"
  }
]
GET/v1/jurisdictions/{code}

Get Jurisdiction

Retrieve a single jurisdiction by its code.

Parameters

codestringrequired

Jurisdiction code (path parameter)

Request
curl https://api.taxlens.getdynamiq.ai/v1/jurisdictions/US-NY-NYC \
  -H "X-API-Key: <your-api-key>"
Response
{
  "id": 3,
  "code": "US-NY-NYC",
  "name": "New York City",
  "jurisdiction_type": "city",
  "path": "US.NY.NYC",
  "parent_id": 2,
  "country_code": "US",
  "subdivision_code": "US-NY",
  "timezone": "America/New_York",
  "currency_code": "USD",
  "status": "active"
}
GET/v1/jurisdictions/{code}/children

Get Children

Retrieve direct child jurisdictions. Use this for progressive disclosure — start from a country and drill down through states/regions to cities.

Parameters

codestringrequired

Parent jurisdiction code (path parameter)

Request
curl https://api.taxlens.getdynamiq.ai/v1/jurisdictions/US/children \
  -H "X-API-Key: <your-api-key>"
Response
[
  {
    "id": 40,
    "code": "US-CA",
    "name": "California",
    "jurisdiction_type": "state",
    "path": "US.CA",
    "parent_id": 1,
    "country_code": "US",
    "subdivision_code": "US-CA",
    "timezone": "America/Los_Angeles",
    "currency_code": "USD",
    "status": "active"
  },
  {
    "id": 41,
    "code": "US-CO",
    "name": "Colorado",
    "jurisdiction_type": "state",
    "path": "US.CO",
    "parent_id": 1,
    "country_code": "US",
    "subdivision_code": "US-CO",
    "timezone": "America/Denver",
    "currency_code": "USD",
    "status": "active"
  },
  {
    "id": 2,
    "code": "US-NY",
    "name": "New York",
    "jurisdiction_type": "state",
    "path": "US.NY",
    "parent_id": 1,
    "country_code": "US",
    "subdivision_code": "US-NY",
    "timezone": "America/New_York",
    "currency_code": "USD",
    "status": "active"
  }
]
GET/v1/jurisdictions/{code}/ancestors

Get Ancestors

Retrieve the full ancestor chain from root to the parent of the given jurisdiction. Useful for building breadcrumb trails. The jurisdiction itself is NOT included.

Parameters

codestringrequired

Jurisdiction code (path parameter)

Request
curl https://api.taxlens.getdynamiq.ai/v1/jurisdictions/US-NY-NYC/ancestors \
  -H "X-API-Key: <your-api-key>"
Response
[
  {
    "id": 1,
    "code": "US",
    "name": "United States",
    "jurisdiction_type": "country",
    "path": "US",
    "parent_id": null,
    "country_code": "US",
    "timezone": "America/New_York",
    "currency_code": "USD",
    "status": "active"
  },
  {
    "id": 2,
    "code": "US-NY",
    "name": "New York",
    "jurisdiction_type": "state",
    "path": "US.NY",
    "parent_id": 1,
    "country_code": "US",
    "subdivision_code": "US-NY",
    "timezone": "America/New_York",
    "currency_code": "USD",
    "status": "active"
  }
]

Coverage Map

GET/v1/map/coverage

Coverage Rollup

Return customer-safe country rollups, admin-region rollups, and local jurisdiction marker GeoJSON for the authenticated organization. This is the same read-only payload behind the in-app coverage map. Use it to show where TaxLens has active data, how deep the jurisdiction graph is by country, and which cities or region-level jurisdictions have marker coordinates. Marker coordinates come from jurisdiction metadata first, then deterministic vendored Natural Earth data when available.

No parameters
Request
curl https://api.taxlens.getdynamiq.ai/v1/map/coverage \
  -H "X-API-Key: <your-api-key>"
Response
{
  "generated_at": "2026-06-01T12:00:00Z",
  "totals": {
    "countries": 245,
    "jurisdictions": 1024,
    "cities": 470,
    "active_rates": 1130,
    "active_rules": 660,
    "sources": 830,
    "city_markers": 430,
    "missing_city_markers": 40,
    "marker_jurisdictions": 500,
    "jurisdiction_markers": 430,
    "missing_jurisdiction_markers": 70
  },
  "countries": [
    {
      "code": "ES",
      "name": "Spain",
      "currency_code": "EUR",
      "jurisdiction_count": 18,
      "subjurisdiction_count": 17,
      "city_count": 6,
      "active_rate_count": 22,
      "active_rule_count": 14,
      "source_count": 9,
      "coverage_score": 84
    }
  ],
  "regions": [
    {
      "code": "ES-CT",
      "name": "Catalonia",
      "country_code": "ES",
      "jurisdiction_type": "region",
      "child_count": 1,
      "active_rate_count": 1,
      "active_rule_count": 2,
      "source_count": 1
    }
  ],
  "city_markers": {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [2.1734, 41.3851]
        },
        "properties": {
          "code": "ES-CT-BCN",
          "name": "Barcelona",
          "country_code": "ES",
          "jurisdiction_type": "city",
          "currency_code": "EUR",
          "active_rate_count": 3,
          "active_rule_count": 4,
          "coordinate_source": "metadata"
        }
      }
    ]
  }
}
GET/v1/map/jurisdictions/{code}

Jurisdiction Dossier

Return a focused dossier for a clicked country, region, city, or marker without recomputing the full global map payload. The response includes hierarchy context, child jurisdictions, direct and inherited effective rates, active rules, monitored sources, the country rollup, and optional marker geometry. This endpoint is useful when a partner wants a drill-down panel or support view that explains why a jurisdiction is covered.

Parameters

codestringrequired

Jurisdiction code to inspect (for example ES, ES-CT, or ES-CT-BCN).

Request
curl https://api.taxlens.getdynamiq.ai/v1/map/jurisdictions/ES-CT-BCN \
  -H "X-API-Key: <your-api-key>"
Response
{
  "generated_at": "2026-06-01T12:00:00Z",
  "jurisdiction": {
    "id": 301,
    "code": "ES-CT-BCN",
    "name": "Barcelona",
    "local_name": "Barcelona",
    "jurisdiction_type": "city",
    "path": "ES.CT.BCN",
    "parent_id": 201,
    "country_code": "ES",
    "subdivision_code": "ES-CT",
    "timezone": "Europe/Madrid",
    "currency_code": "EUR",
    "status": "active",
    "created_at": "2026-03-12T00:00:00Z",
    "updated_at": "2026-05-28T20:40:00Z"
  },
  "ancestors": [
    {
      "id": 100,
      "code": "ES",
      "name": "Spain",
      "local_name": "Espana",
      "jurisdiction_type": "country",
      "path": "ES",
      "parent_id": null,
      "country_code": "ES",
      "subdivision_code": null,
      "timezone": "Europe/Madrid",
      "currency_code": "EUR",
      "status": "active",
      "created_at": "2026-03-12T00:00:00Z",
      "updated_at": "2026-05-28T20:40:00Z"
    },
    {
      "id": 201,
      "code": "ES-CT",
      "name": "Catalonia",
      "local_name": "Catalunya",
      "jurisdiction_type": "region",
      "path": "ES.CT",
      "parent_id": 100,
      "country_code": "ES",
      "subdivision_code": "ES-CT",
      "timezone": "Europe/Madrid",
      "currency_code": "EUR",
      "status": "active",
      "created_at": "2026-03-12T00:00:00Z",
      "updated_at": "2026-05-28T20:40:00Z"
    }
  ],
  "children": [],
  "country": {
    "code": "ES",
    "name": "Spain",
    "currency_code": "EUR",
    "jurisdiction_count": 18,
    "subjurisdiction_count": 17,
    "city_count": 6,
    "active_rate_count": 22,
    "active_rule_count": 14,
    "source_count": 9,
    "coverage_score": 84
  },
  "direct_rates": [
    {
      "id": 5117,
      "jurisdiction_id": 301,
      "jurisdiction_code": "ES-CT-BCN",
      "tax_category_id": 44,
      "tax_category_code": "tourism_flat_person_night",
      "rate_type": "tiered",
      "rate_value": null,
      "currency_code": "EUR",
      "tiers": [{ "min": 4, "value": 6.6 }],
      "tier_type": "single_amount",
      "percentage_basis": null,
      "basis_value": null,
      "basis_currency": null,
      "enacted_date": "2026-02-01",
      "effective_start": "2026-04-01",
      "effective_end": null,
      "applicability_start": null,
      "announcement_date": "2026-02-01",
      "calculation_order": 100,
      "base_includes": [],
      "legal_reference": "Ley 2/2026 and ATC IEET tariff table",
      "legal_uri": "https://atc.gencat.cat/",
      "source_url": "https://atc.gencat.cat/",
      "authority_name": "Ajuntament de Barcelona / Agencia Tributaria de Catalunya",
      "version": 1,
      "supersedes_id": null,
      "status": "scheduled",
      "collection_model": null,
      "taxable_amount_rule": null
    }
  ],
  "effective_rates": [
    {
      "id": 120,
      "tax_category_code": "vat_reduced",
      "tax_category_name": "VAT / Sales Tax (reduced rate)",
      "rate_type": "percentage",
      "rate_value": "0.10",
      "currency_code": null,
      "tiers": null,
      "effective_start": "2024-01-01",
      "effective_end": null,
      "legal_reference": "Ley 37/1992, Articulo 91.uno.2.2",
      "authority_name": "Agencia Tributaria",
      "source_url": "https://sede.agenciatributaria.gob.es/",
      "attached_to_jurisdiction_id": 100,
      "attached_to_jurisdiction_code": "ES",
      "attached_to_jurisdiction_name": "Spain",
      "attached_to_jurisdiction_type": "country",
      "is_inherited": true
    }
  ],
  "rules": [
    {
      "id": 1361,
      "tax_rate_id": 5117,
      "jurisdiction_id": 301,
      "jurisdiction_code": "ES-CT-BCN",
      "rule_type": "cap",
      "priority": 100,
      "name": "Barcelona IEET - 7-night cap per continuous stay",
      "description": "Caps city tourism tax at 7 nights.",
      "conditions": { "operator": "AND", "rules": [] },
      "action": { "type": "cap", "max_nights": 7 },
      "effective_start": "2026-04-01",
      "effective_end": null,
      "enacted_date": "2026-02-01",
      "legal_reference": "Ley 2/2026",
      "legal_uri": "https://atc.gencat.cat/",
      "authority_name": "Ajuntament de Barcelona",
      "status": "active",
      "version": 1,
      "supersedes_id": null
    }
  ],
  "sources": [
    {
      "id": 88,
      "jurisdiction_code": "ES-CT-BCN",
      "url": "https://atc.gencat.cat/",
      "source_type": "tax_authority",
      "language": "es",
      "check_frequency_days": 7,
      "last_checked_at": "2026-05-28T20:40:00Z",
      "last_content_hash": "sha256:example",
      "status": "active",
      "created_at": "2026-03-12T00:00:00Z",
      "updated_at": "2026-05-28T20:40:00Z"
    }
  ],
  "marker": {
    "type": "Feature",
    "geometry": {
      "type": "Point",
      "coordinates": [2.1734, 41.3851]
    },
    "properties": {
      "code": "ES-CT-BCN",
      "name": "Barcelona",
      "country_code": "ES",
      "jurisdiction_type": "city",
      "currency_code": "EUR",
      "active_rate_count": 3,
      "active_rule_count": 4,
      "coordinate_source": "metadata"
    }
  },
  "metadata": {
    "map_centroid": { "lat": 41.3851, "lng": 2.1734 }
  }
}

Tax Rates

GET/v1/rates

List Rates

Retrieve a paginated list of tax rates. Rates define the actual tax amounts: percentage, flat, or tiered.

Parameters

jurisdiction_codestringoptional

Filter by jurisdiction code

category_codestringoptional

Filter by tax category code

statusstringoptional

Filter: active, draft, approved, scheduled, superseded, rejected, needs_review

effective_datedateoptional

Filter rates effective on this date

limitintegeroptional

Max results (1-500)

Default: 100

offsetintegeroptional

Pagination offset

Default: 0

Request
curl "https://api.taxlens.getdynamiq.ai/v1/rates?jurisdiction_code=US-NY-NYC&status=active" \
  -H "X-API-Key: <your-api-key>"
Response
[
  {
    "id": 3,
    "jurisdiction_code": "US-NY-NYC",
    "tax_category_code": "occ_pct",
    "rate_type": "percentage",
    "rate_value": 0.05875,
    "currency_code": "USD",
    "calculation_order": 30,
    "effective_start": "2024-01-01",
    "status": "active",
    "legal_reference": "NYC Admin Code §11-2502",
    "authority_name": "NYC Department of Finance",
    "source_url": "https://www.nyc.gov/site/finance/property/hotel-room-occupancy-tax.page"
  }
]
GET/v1/rates/lookup

Lookup Rates

Look up all active rates for a jurisdiction on a given date, including inherited rates from parent jurisdictions. Returns the combined percentage rate — useful for displaying the total tax rate to users before they book.

Parameters

jurisdiction_codestringrequired

Jurisdiction code to look up

effective_datedateoptional

Date to check (defaults to today)

Request
curl "https://api.taxlens.getdynamiq.ai/v1/rates/lookup?jurisdiction_code=US-NY-NYC" \
  -H "X-API-Key: <your-api-key>"
Response
{
  "jurisdiction": {
    "code": "US-NY-NYC",
    "name": "New York City",
    "path": "US.NY.NYC"
  },
  "date": "2026-03-12",
  "rates": [
    {
      "jurisdiction_code": "US-NY",
      "tax_category_code": "vat_standard",
      "rate_type": "percentage",
      "rate_value": 0.04,
      "currency_code": "USD",
      "status": "active",
      "effective_start": "2024-01-01"
    },
    {
      "jurisdiction_code": "US-NY-NYC",
      "tax_category_code": "occ_pct",
      "rate_type": "percentage",
      "rate_value": 0.05875,
      "currency_code": "USD",
      "status": "active",
      "effective_start": "2024-01-01"
    }
  ],
  "combined_percentage_rate": 0.14375
}
GET/v1/rates/{rate_id}

Get Rate

Retrieve a single tax rate by ID.

Parameters

rate_idintegerrequired

Rate ID (path parameter)

Request
curl https://api.taxlens.getdynamiq.ai/v1/rates/3 \
  -H "X-API-Key: <your-api-key>"
Response
{
  "id": 3,
  "jurisdiction_code": "US-NY-NYC",
  "tax_category_code": "occ_pct",
  "rate_type": "percentage",
  "rate_value": 0.05875,
  "currency_code": "USD",
  "tiers": null,
  "calculation_order": 30,
  "base_includes": ["base_amount"],
  "effective_start": "2024-01-01",
  "effective_end": null,
  "status": "active",
  "legal_reference": "NYC Admin Code §11-2502",
  "authority_name": "NYC Department of Finance",
  "source_url": "https://www.nyc.gov/site/finance/property/hotel-room-occupancy-tax.page"
}

Tax Rules

GET/v1/rules

List Rules

Retrieve tax rules. Rules modify how rates are applied: exemptions (zero out tax), reductions (percentage discount), caps (max nights/amount), surcharges (additional %), and overrides.

Parameters

jurisdiction_codestringoptional

Filter by jurisdiction code

rule_typestringoptional

Filter: exemption, reduction, surcharge, cap, override, condition, threshold

statusstringoptional

Filter by status

tax_rate_idintegeroptional

Filter by associated rate ID

limitintegeroptional

Max results (1-500)

Default: 100

offsetintegeroptional

Pagination offset

Default: 0

Request
curl "https://api.taxlens.getdynamiq.ai/v1/rules?jurisdiction_code=HR-19-DBV" \
  -H "X-API-Key: <your-api-key>"
Response
[
  {
    "id": 21,
    "jurisdiction_code": "HR-19-DBV",
    "rule_type": "exemption",
    "priority": 100,
    "name": "Children Under 12 Exemption",
    "conditions": {
      "operator": "AND",
      "rules": [
        { "field": "guest_age", "op": "<", "value": 12 }
      ]
    },
    "action": { "type": "exempt" },
    "effective_start": "2025-04-01",
    "status": "active"
  },
  {
    "id": 22,
    "jurisdiction_code": "HR-19-DBV",
    "rule_type": "reduction",
    "priority": 90,
    "name": "Youth 50% Reduction (Ages 12-17)",
    "conditions": {
      "operator": "AND",
      "rules": [
        { "field": "guest_age", "op": ">=", "value": 12 },
        { "field": "guest_age", "op": "<=", "value": 17 }
      ]
    },
    "action": { "reduction_percent": 0.5 },
    "effective_start": "2025-04-01",
    "status": "active"
  }
]
GET/v1/rules/{rule_id}

Get Rule

Retrieve a single tax rule by ID.

Parameters

rule_idintegerrequired

Rule ID (path parameter)

Request
curl https://api.taxlens.getdynamiq.ai/v1/rules/21 \
  -H "X-API-Key: <your-api-key>"
Response
{
  "id": 21,
  "jurisdiction_code": "HR-19-DBV",
  "rule_type": "exemption",
  "priority": 100,
  "name": "Children Under 12 Exemption",
  "conditions": {
    "operator": "AND",
    "rules": [
      { "field": "guest_age", "op": "<", "value": 12 }
    ]
  },
  "action": { "type": "exempt" },
  "effective_start": "2025-04-01",
  "effective_end": null,
  "status": "active"
}

Bookings

POST/v1/bookings

Create Booking

Persist a tax calculation as a booking. The endpoint runs the calculation server-side (same engine as /v1/tax/calculate), stores the request + response snapshots, and returns the booking record. Pass `property_id` to use a managed property's jurisdiction, tax-driver, property default seller, and property default supplier when the booking omits those fields. Pass an `idempotency_key` to make retries safe: re-POSTing with the same key + org returns the original booking with HTTP 200 instead of creating a duplicate (201 only on first write). Bookings are org-scoped — the org is resolved from the JWT and cannot be set by the caller.

Request Body

calculationobjectrequired

A full TaxCalculationRequest payload (same schema as POST /v1/tax/calculate). Re-run server-side to produce the booking totals. For e-invoicing it also accepts `buyer_name` (EN 16931 BT-44) and `buyer_address` ({line1, line2, city, region, postal_code, country_code} — BG-8); these don't affect tax but are required for a PEPPOL-valid invoice.

property_idintegeroptional

Managed property/listing ID. If calculation omits jurisdiction or tax drivers, the property fills them. If booking omits legal issuer fields, the property default seller and property default supplier can fill them before org-level fallbacks.

legal_issuer_idintegeroptional

The legal entity that issues the invoice (must belong to your org). Explicit value wins; when omitted TaxLens uses the property default seller, then the org default issuer. Drives the invoice seller party.

buyer_referencestringoptional

EN 16931 BT-10 buyer reference (PO number, XRechnung Leitweg-ID, French B2G reference). Stored verbatim for the invoice. Required for a B2B PEPPOL invoice (PEPPOL-EN16931-R003).

buyer_endpoint_idstringoptional

Buyer PEPPOL electronic address (EN 16931 BT-49) — the network routing address. REQUIRED for a B2B PEPPOL invoice (PEPPOL-EN16931-R010); without it /issue and /document/ubl are blocked (422).

buyer_endpoint_schemestringoptional

4-digit EAS/ICD scheme code for buyer_endpoint_id (e.g. 9930 for VAT, 0204 for Leitweg-ID). Required alongside buyer_endpoint_id.

supplier_legal_issuer_idintegeroptional

Host/supplier legal entity for a self-billed (389) invoice. Explicit value wins; when omitted TaxLens uses the property default supplier. The platform/OTA issues IN THIS ENTITY'S NAME and burns the host's fiscal sequence at /issue with self_billed=true.

payment_termsstringoptional

EN 16931 BT-20 payment terms. Defaults to "Due on receipt"; required (with a positive total) for B2B (BR-CO-25).

external_referencestringoptional

Your internal booking ID (max 200 chars). Surfaced in list filters and used for reconciliation against your PMS/OTA.

idempotency_keystringoptional

Optional client-generated key (max 200 chars). Re-POSTing with the same key + org returns the existing booking (HTTP 200) instead of creating a duplicate.

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/bookings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "external_reference": "PMS-2026-00482",
    "idempotency_key": "pms-2026-00482-v1",
    "property_id": 42,
    "calculation": {
      "jurisdiction_code": "US-NY-NYC",
      "stay_date": "2026-06-15",
      "checkout_date": "2026-06-18",
      "nightly_rate": 250,
      "currency": "USD",
      "nights": 3,
      "number_of_guests": 2,
      "property_type": "hotel",
      "buyer_name": "ACME GmbH",
      "buyer_address": { "line1": "Hauptstrasse 1", "city": "Berlin", "postal_code": "10115", "country_code": "DE" }
    }
  }'
Response
{
  "id": 4821,
  "org_id": 17,
  "user_id": 42,
  "external_reference": "PMS-2026-00482",
  "idempotency_key": "pms-2026-00482-v1",
  "status": "confirmed",
  "total_tax": "87.37",
  "total_with_tax": "837.37",
  "currency": "USD",
  "request": {
    "jurisdiction_code": "US-NY-NYC",
    "stay_date": "2026-06-15",
    "checkout_date": "2026-06-18",
    "nightly_rate": 250.0,
    "currency": "USD",
    "nights": 3,
    "number_of_guests": 2,
    "property_type": "hotel"
  },
  "response": {
    "calculation_id": "calc_97d7d87e3cce7947c44e9fa20100f416",
    "jurisdiction": { "code": "US-NY-NYC", "name": "New York City", "path": "US.NY.NYC" },
    "input": { "nightly_rate": 250.0, "nights": 3, "subtotal": 750.0, "currency": "USD", "property_type": "hotel" },
    "tax_breakdown": {
      "components": [
        { "name": "VAT / Sales Tax (standard)", "rate": 0.04, "rate_type": "percentage", "taxable_amount": "750", "tax_amount": "30.00" },
        { "name": "Municipal Surcharge (%)", "rate": 0.00375, "rate_type": "percentage", "taxable_amount": "750", "tax_amount": "2.81" },
        { "name": "Occupancy Tax (% of room)", "rate": 0.05875, "rate_type": "percentage", "taxable_amount": "750", "tax_amount": "44.06" },
        { "name": "Occupancy Tax (flat per night)", "rate": 0.0, "rate_type": "tiered", "taxable_amount": null, "tax_amount": "6.00" },
        { "name": "Convention Center Tax (flat)", "rate": 1.5, "rate_type": "flat", "taxable_amount": null, "tax_amount": "4.50" },
        { "name": "VAT / Sales Tax (standard)", "rate": 0.0, "rate_type": "percentage", "taxable_amount": "750", "tax_amount": "0.00" }
      ],
      "total_tax": "87.37",
      "effective_rate": "0.1164933333333333333333333333",
      "currency": "USD"
    },
    "total_with_tax": "837.37"
  },
  "metadata": {},
  "created_at": "2026-05-21T10:29:52.943980Z",
  "updated_at": "2026-05-21T10:29:52.943980Z",
  "voided_at": null
}
GET/v1/bookings/{booking_id}

Get Booking

Retrieve a single booking by its numeric ID. Returns the full record including the original request snapshot and the calculation response that was persisted at create time. Org-scoped — bookings belonging to other organizations return 404, never 403, to avoid leaking existence.

Parameters

booking_idintegerrequired

Numeric booking ID (the `id` returned from POST /v1/bookings).

Request
curl https://api.taxlens.getdynamiq.ai/v1/bookings/4821 \
  -H "Authorization: Bearer <token>"
Response
{
  "id": 4821,
  "org_id": 17,
  "user_id": 42,
  "external_reference": "PMS-2026-00482",
  "idempotency_key": "pms-2026-00482-v1",
  "status": "confirmed",
  "total_tax": "87.37",
  "total_with_tax": "837.37",
  "currency": "USD",
  "request": {
    "jurisdiction_code": "US-NY-NYC",
    "stay_date": "2026-06-15",
    "checkout_date": "2026-06-18",
    "nightly_rate": 250.0,
    "currency": "USD",
    "nights": 3,
    "number_of_guests": 2,
    "property_type": "hotel"
  },
  "response": {
    "calculation_id": "calc_97d7d87e3cce7947c44e9fa20100f416",
    "jurisdiction": { "code": "US-NY-NYC", "name": "New York City", "path": "US.NY.NYC" },
    "tax_breakdown": {
      "components": [],
      "total_tax": "87.37",
      "effective_rate": "0.1164933333333333333333333333",
      "currency": "USD"
    },
    "total_with_tax": "837.37"
  },
  "metadata": {},
  "created_at": "2026-05-21T10:29:52.943980Z",
  "updated_at": "2026-05-21T10:29:52.943980Z",
  "voided_at": null
}
GET/v1/bookings

List Bookings

List bookings for the caller's organization, ordered most-recent first. Supports filtering by status, external reference, and creation date range, plus cursor-based pagination. The cursor is the last booking ID returned — pass it back in the next request to fetch the following page. Returns `next_cursor: null` when there are no more results.

Parameters

statusstringoptional

Filter by status: `confirmed`, `adjusted`, or `voided`.

external_referencestringoptional

Exact-match filter on the customer's reference ID supplied at create time.

from_datedatetimeoptional

ISO 8601 datetime. Returns bookings with `created_at >= from_date`.

to_datedatetimeoptional

ISO 8601 datetime. Returns bookings with `created_at <= to_date`.

limitintegeroptional

Page size (1-200). Default 50.

Default: 50

cursorintegeroptional

Booking ID to paginate before. Pass the `next_cursor` from the previous response.

Request
curl "https://api.taxlens.getdynamiq.ai/v1/bookings?status=confirmed&from_date=2026-05-01T00:00:00Z&limit=25" \
  -H "Authorization: Bearer <token>"
Response
{
  "items": [
    {
      "id": 4821,
      "external_reference": "PMS-2026-00482",
      "status": "confirmed",
      "total_tax": "87.37",
      "total_with_tax": "837.37",
      "currency": "USD",
      "request": {
        "jurisdiction_code": "US-NY-NYC",
        "stay_date": "2026-06-15",
        "nightly_rate": 250.0,
        "currency": "USD",
        "nights": 3,
        "property_type": "hotel"
      },
      "response": {
        "calculation_id": "calc_97d7d87e3cce7947c44e9fa20100f416",
        "jurisdiction": { "code": "US-NY-NYC", "name": "New York City", "path": "US.NY.NYC" },
        "tax_breakdown": { "components": [], "total_tax": "87.37", "effective_rate": "0.1164933333333333333333333333", "currency": "USD" },
        "total_with_tax": "837.37"
      },
      "metadata": {},
      "created_at": "2026-05-21T10:29:52.943980Z",
      "updated_at": "2026-05-21T10:29:52.943980Z",
      "voided_at": null
    }
  ],
  "next_cursor": "4820"
}
PATCH/v1/bookings/{booking_id}

Adjust Booking

Re-run the tax engine for an existing booking when a tax-affecting input changes, such as dates, jurisdiction, nightly price, guest count, property type, or buyer tax context. The booking remains the same row, `status` becomes `adjusted`, totals and the persisted calculation snapshots are replaced, and the prior calculation is appended to `metadata.adjustment_history`. Adjustments are blocked after void, full refund, any partial refund, or issued invoice; correct issued invoices with a credit note plus a new booking.

Parameters

booking_idintegerrequired

Numeric booking ID to adjust.

Request Body

calculationobjectrequired

Full TaxCalculationRequest replacement. Send the complete recalculation input, not only changed fields.

reasonstringoptional

Optional operator/customer reason stored in `metadata.adjustment_history[].reason`.

Request
curl -X PATCH https://api.taxlens.getdynamiq.ai/v1/bookings/4821 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "reason": "nightly rate corrected after PMS sync",
    "calculation": {
      "jurisdiction_code": "US-NY-NYC",
      "stay_date": "2026-06-15",
      "checkout_date": "2026-06-18",
      "nightly_rate": 275.0,
      "currency": "USD",
      "nights": 3,
      "number_of_guests": 2,
      "property_type": "hotel"
    }
  }'
Response
{
  "id": 4821,
  "org_id": 17,
  "user_id": 42,
  "external_reference": "PMS-2026-00482",
  "idempotency_key": "pms-2026-00482-v1",
  "status": "adjusted",
  "total_tax": "96.11",
  "total_with_tax": "921.11",
  "currency": "USD",
  "request": {
    "jurisdiction_code": "US-NY-NYC",
    "stay_date": "2026-06-15",
    "checkout_date": "2026-06-18",
    "nightly_rate": 275.0,
    "currency": "USD",
    "nights": 3,
    "number_of_guests": 2,
    "property_type": "hotel"
  },
  "response": {
    "calculation_id": "calc_2f0986f5a3f2b6c99f8a0d7452a27ed1",
    "jurisdiction": { "code": "US-NY-NYC", "name": "New York City", "path": "US.NY.NYC" },
    "tax_breakdown": {
      "components": [],
      "total_tax": "96.11",
      "effective_rate": "0.1164969696969696969696969697",
      "currency": "USD"
    },
    "total_with_tax": "921.11"
  },
  "metadata": {
    "adjustment_history": [
      {
        "at": "2026-05-22T11:42:31.120000+00:00",
        "reason": "nightly rate corrected after PMS sync",
        "previous_request": {
          "jurisdiction_code": "US-NY-NYC",
          "stay_date": "2026-06-15",
          "checkout_date": "2026-06-18",
          "nightly_rate": 250.0,
          "currency": "USD",
          "nights": 3,
          "number_of_guests": 2,
          "property_type": "hotel"
        },
        "previous_total_tax": "87.37"
      }
    ]
  },
  "created_at": "2026-05-21T10:29:52.943980Z",
  "updated_at": "2026-05-22T11:42:31.120000Z",
  "voided_at": null
}
DELETE/v1/bookings/{booking_id}

Void Booking

Void a booking. Voids are sticky and idempotent — a second DELETE on the same booking is a no-op. The booking row is NOT deleted; its `status` flips to `voided` and `voided_at` is set, preserving the audit trail. Voided bookings continue to appear in list responses (filter with `?status=voided`) and remain retrievable via GET. Returns HTTP 204 with no response body on success.

Parameters

booking_idintegerrequired

Numeric booking ID to void.

Request
curl -X DELETE https://api.taxlens.getdynamiq.ai/v1/bookings/4821 \
  -H "Authorization: Bearer <token>" -i
Response
HTTP/1.1 204 No Content
POST/v1/bookings/{booking_id}/refund

Refund Booking

Process a partial or full refund on a persisted booking. The engine computes a proportional tax delta per jurisdiction — i.e. how much of the originally-collected tax should be returned to the guest / clawed back from authorities. Idempotent via `refund_idempotency_key`: re-POSTing with the same key returns the prior result. A full refund (`refund_amount >= booking.total_with_tax`) flips status to `refund`; `voided_at` remains reserved for explicit voids. Partial refunds preserve the booking's prior status. Refund history is appended to `booking.metadata.refund_history` (a JSONB array).

The refund uses the ORIGINAL calc's tax components (not a re-calc against current rates) — the booking already had its tax fixed at original calc time; refund returns a proportional fraction of that tax. Jurisdictions that tax refunds at the refund-date rate (rare) would require a per-jurisdiction rule — deferred until we encounter one in primary sources.

Parameters

booking_idintegerrequired

Numeric booking ID to refund.

Request Body

refund_amountdecimalrequired

Amount to refund in booking currency. Must be > 0 and ≤ booking.total_with_tax.

refund_currencystringrequired

ISO 4217. Must match the booking's currency.

refund_reasonstringrequired

guest_cancel | courtesy | no_show | duplicate_charge | downgrade | other

refund_datedateoptional

Effective date of the refund. Defaults to today. Matters for jurisdictions that tax refunds at the refund-date rate.

refund_idempotency_keystringoptional

If provided, re-POSTing with the same key returns the prior result (idempotent). Max 200 chars.

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/bookings/4821/refund \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "refund_amount": 100.00,
    "refund_currency": "USD",
    "refund_reason": "guest_cancel",
    "refund_idempotency_key": "refund-4821-v1"
  }'
Response
{
  "booking_id": 4821,
  "refund_amount": 100.00,
  "refund_currency": "USD",
  "refund_reason": "guest_cancel",
  "refund_date": "2026-05-22",
  "refund_idempotency_key": "refund-4821-v1",
  "refund_status": "partial",
  "tax_delta": [
    {
      "category_code": "vat_standard",
      "jurisdiction_code": "US-NY",
      "original_tax": "30.00",
      "refunded_tax": "3.58",
      "notes": null
    },
    {
      "category_code": "municipal_pct",
      "jurisdiction_code": "US-NY-NYC",
      "original_tax": "2.81",
      "refunded_tax": "0.34",
      "notes": null
    },
    {
      "category_code": "occ_pct",
      "jurisdiction_code": "US-NY-NYC",
      "original_tax": "44.06",
      "refunded_tax": "5.26",
      "notes": null
    },
    {
      "category_code": "occ_flat_night",
      "jurisdiction_code": "US-NY-NYC",
      "original_tax": "6.00",
      "refunded_tax": "0.72",
      "notes": null
    },
    {
      "category_code": "convention_flat",
      "jurisdiction_code": "US-NY-NYC",
      "original_tax": "4.50",
      "refunded_tax": "0.54",
      "notes": null
    }
  ],
  "total_tax_refunded": "10.44",
  "new_booking_status": "confirmed",
  "refunded_at": "2026-05-22T14:23:45.123Z"
}

Properties

POST/v1/properties

Create Property

Create an org-scoped managed property/listing. TaxLens geocodes the address, resolves the closest jurisdiction, stores mapping confidence, and keeps property tax-driver fields that can prefill future bookings. Properties are durable inventory records; historical booking snapshots do not change when a property is remapped.

Request Body

external_referencestringoptional

Your PMS/listing/property ID for reconciliation.

namestringrequired

Display name for the property.

address_line1stringrequired

Street address.

citystringoptional

City/locality.

regionstringoptional

State, province, region, or county.

postal_codestringoptional

Postal code used for geocoding.

country_codestringrequired

ISO 3166-1 alpha-2 country code.

jurisdiction_codestringoptional

Optional TaxLens jurisdiction code to pin deterministically when the customer already knows the exact local jurisdiction.

property_typestringoptional

Hotel, vacation rental, apartment hotel, villa, hostel, etc.

star_ratingintegeroptional

1-5 where jurisdictional rules depend on classification.

property_postal_codestringoptional

Tax-zone postal code when different from the mailing postal code.

default_legal_issuer_idintegeroptional

Optional org-owned legal issuer used as the property default seller when bookings from this property omit legal_issuer_id. Setting or changing this requires org-admin permissions.

default_supplier_legal_issuer_idintegeroptional

Optional org-owned host/owner issuer used as the property default supplier for self-billed 389 flows when bookings omit supplier_legal_issuer_id. Setting or changing this requires org-admin permissions.

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/properties \
  -H "Authorization: Bearer $TAXLENS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "external_reference": "PMS-HOTEL-42",
    "name": "Downtown Hotel",
    "address_line1": "350 5th Ave",
    "city": "New York",
    "region": "NY",
    "postal_code": "10118",
    "country_code": "US",
    "jurisdiction_code": "US-NY-NYC",
    "property_type": "hotel",
    "star_rating": 4,
    "default_legal_issuer_id": 7,
    "default_supplier_legal_issuer_id": 18
  }'
Response
{
  "id": 42,
  "external_reference": "PMS-HOTEL-42",
  "name": "Downtown Hotel",
  "jurisdiction_code": "US-NY-NYC-MAN",
  "jurisdiction_confidence": "high",
  "mapping_status": "mapped",
  "tax_driver_status": "complete",
  "property_type": "hotel",
  "star_rating": 4,
  "default_legal_issuer_id": 7,
  "default_supplier_legal_issuer_id": 18
}
POST/v1/properties/bulk-map

Bulk Map Properties

Create up to 500 managed properties in one request. Each property is geocoded, jurisdiction-mapped, and stored as durable inventory. This is the recommended import path for PMS/channel-manager listing syncs. Org-admin permissions are required only when a row sets non-null legal issuer defaults.

Request Body

propertiesarrayrequired

1-500 PropertyCreate objects. Supports the same fields as Create Property, including default_legal_issuer_id and default_supplier_legal_issuer_id.

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/properties/bulk-map \
  -H "Authorization: Bearer $TAXLENS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "properties": [
      {
        "external_reference": "GUESTY-ROME-1",
        "name": "Rome Host Unit",
        "address_line1": "Via Roma 1",
        "country_code": "IT",
        "property_type": "apartment",
        "default_supplier_legal_issuer_id": 18
      }
    ]
  }'
Response
{
  "items": [
    {
      "id": 42,
      "external_reference": "GUESTY-ROME-1",
      "name": "Rome Host Unit",
      "jurisdiction_code": "IT-RM",
      "mapping_status": "mapped",
      "default_legal_issuer_id": null,
      "default_supplier_legal_issuer_id": 18
    }
  ],
  "mapped": 1,
  "unresolved": 0
}
GET/v1/properties

List Properties

List the caller organization's managed properties. Filter by mapping status, country, jurisdiction, or external reference. Use this to keep PMS/listing inventory synchronized before creating bookings.

Parameters

mapping_statusstringoptional

mapped, unresolved, or stale.

country_codestringoptional

ISO country filter.

jurisdiction_codestringoptional

Exact TaxLens jurisdiction code.

external_referencestringoptional

Your property/listing ID.

Request
curl "https://api.taxlens.getdynamiq.ai/v1/properties?mapping_status=mapped&country_code=US" \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
{
  "items": [
    { "id": 42, "name": "Downtown Hotel", "jurisdiction_code": "US-NY-NYC-MAN", "mapping_status": "mapped" }
  ],
  "total": 1
}
POST/v1/properties/{property_id}/remap

Remap Property

Re-run address geocoding and jurisdiction resolution for one managed property. Future bookings can use the refreshed mapping; existing booking and invoice snapshots remain frozen.

Parameters

property_idintegerrequired

Managed property ID owned by the active organization.

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/properties/42/remap \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
{
  "id": 42,
  "mapping_status": "mapped",
  "jurisdiction_code": "US-NY-NYC-MAN",
  "last_mapped_at": "2026-06-04T12:00:00Z"
}

E-invoicing

POST/v1/bookings/{booking_id}/issue

Issue Invoice

Turn a booking into an immutable fiscal document. Runs the EN 16931 / PEPPOL send-readiness pre-check, burns a gapless fiscal number (per issuer / document-type / series / year), and freezes the document. Idempotent — re-issuing returns the same invoice. 422 if not send-ready, 409 if the booking is voided. For self-billed 389 flows, set supplier_legal_issuer_id on the booking first; otherwise the platform issuer is used for backward compatibility. Requires org owner or admin.

Parameters

booking_idintegerrequired

Booking ID.

Request Body

seriesstringoptional

Optional numbering series prefix (Italy/Spain/Mexico keep parallel streams). Defaults to none.

self_billedbooleanoptional

Issue as a self-billed invoice (UNCL1001 389) — a platform/OTA issuing in the supplier's name (merchant-of-record). Defaults to false (380).

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/bookings/123/issue \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TAXLENS_TOKEN" \
  -d '{ "series": "" }'
Response
{
  "id": 9,
  "org_id": 7,
  "booking_id": 123,
  "legal_issuer_id": 1,
  "document_type": "380",
  "series": "",
  "fiscal_year": 2026,
  "sequence_number": 1,
  "invoice_number": "2026-000001",
  "currency": "EUR",
  "grand_total": "495.00",
  "status": "issued",
  "preceding_invoice_id": null,
  "issued_at": "2026-05-29T10:00:00Z",
  "cancelled_at": null,
  "created_at": "2026-05-29T10:00:00Z",
  "updated_at": "2026-05-29T10:00:00Z",
  "document": null
}
POST/v1/invoices/{invoice_id}/credit-note

Issue Credit Note

Issue a credit note (UNCL1001 381) that reverses an issued invoice. Burns a fiscal number from the credit-note sequence, references the original (BT-25), marks the original 'credited', and serializes as a PEPPOL CreditNote document. Idempotent (one per booking). Owner/admin.

Parameters

invoice_idintegerrequired

Issued invoice ID to reverse.

Request Body

reasonstringoptional

Audit-trail reason for the credit.

seriesstringoptional

Optional credit-note numbering series.

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/invoices/9/credit-note \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TAXLENS_TOKEN" \
  -d '{ "reason": "guest cancelled", "series": "" }'
Response
{
  "id": 10,
  "org_id": 7,
  "booking_id": 123,
  "legal_issuer_id": 1,
  "document_type": "381",
  "series": "",
  "fiscal_year": 2026,
  "sequence_number": 1,
  "invoice_number": "2026-000001",
  "currency": "EUR",
  "grand_total": "495.00",
  "status": "issued",
  "preceding_invoice_id": 9,
  "issued_at": "2026-05-29T10:05:00Z",
  "cancelled_at": null,
  "created_at": "2026-05-29T10:05:00Z",
  "updated_at": "2026-05-29T10:05:00Z",
  "document": null
}
GET/v1/invoices

List Issued Invoices

List issued fiscal invoices for your org (newest first). Filter by booking_id; cursor-paginate via next_cursor.

Parameters

booking_idintegeroptional

Filter to one booking.

limitintegeroptional

Page size (1-200, default 50).

cursorintegeroptional

Pagination cursor (last id seen).

Request
curl https://api.taxlens.getdynamiq.ai/v1/invoices?booking_id=123 \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
{
  "items": [
    {
      "id": 9,
      "org_id": 7,
      "booking_id": 123,
      "legal_issuer_id": 1,
      "document_type": "380",
      "series": "",
      "fiscal_year": 2026,
      "sequence_number": 1,
      "invoice_number": "2026-000001",
      "currency": "EUR",
      "grand_total": "495.00",
      "status": "issued",
      "preceding_invoice_id": null,
      "issued_at": "2026-05-29T10:00:00Z",
      "cancelled_at": null,
      "created_at": "2026-05-29T10:00:00Z",
      "updated_at": "2026-05-29T10:00:00Z",
      "document": null
    }
  ],
  "next_cursor": null
}
GET/v1/invoices/{invoice_id}

Get Issued Invoice

Fetch one issued invoice including its frozen EN 16931 document. The document never changes after issuance — booking edits don't touch it.

Parameters

invoice_idintegerrequired

Issued invoice ID.

Request
curl https://api.taxlens.getdynamiq.ai/v1/invoices/9 \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
{
  "id": 9,
  "org_id": 7,
  "booking_id": 123,
  "legal_issuer_id": 1,
  "document_type": "380",
  "series": "",
  "fiscal_year": 2026,
  "sequence_number": 1,
  "invoice_number": "2026-000001",
  "currency": "EUR",
  "grand_total": "495.00",
  "status": "issued",
  "preceding_invoice_id": null,
  "issued_at": "2026-05-29T10:00:00Z",
  "cancelled_at": null,
  "created_at": "2026-05-29T10:00:00Z",
  "updated_at": "2026-05-29T10:00:00Z",
  "document": {
    "document": {
      "type_code": "380",
      "type_name": "Commercial invoice",
      "issue_date": "2026-05-29",
      "currency": "EUR"
    },
    "seller": { "legal_name": "Acme Hotel SRL" },
    "buyer": { "name": "ACME GmbH" },
    "totals": { "grand_total": "495.00", "currency": "EUR" },
    "validation": { "is_valid": true, "violations": [] }
  }
}
GET/v1/invoices/{invoice_id}/ubl

Get Issued Invoice UBL

Serialize the FROZEN issued document to UBL 2.1 / PEPPOL BIS 3.0 XML, with the real fiscal invoice_number as cbc:ID (the send-ready artifact, vs. the booking-level UBL's provisional BKG-{booking_id}).

Parameters

invoice_idintegerrequired

Issued invoice ID.

Request
curl https://api.taxlens.getdynamiq.ai/v1/invoices/9/ubl \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
<?xml version="1.0"?><Invoice ...><cbc:ID>2026-000001</cbc:ID>...</Invoice>
PATCH/v1/bookings/{booking_id}/invoice-details

Update Invoice Details

Set invoice-only metadata on an existing booking WITHOUT re-running the tax engine: buyer name (BT-44), buyer address (BG-8), buyer reference (BT-10), and/or the issuing legal entity. This is how a booking becomes PEPPOL send-ready after creation (e.g. you configured your issuer after the booking was made). Partial update — only fields you send are changed. Does not touch totals or status and does not append to adjustment history. Blocked (409) on voided/refunded bookings; use this instead of `adjust` when only invoice metadata changes.

Parameters

booking_idintegerrequired

Booking ID.

Request Body

buyer_namestringoptional

Buyer legal name (EN 16931 BT-44).

buyer_endpoint_idstringoptional

Buyer PEPPOL electronic address (BT-49) — required to make a B2B invoice send-ready (R010).

buyer_endpoint_schemestringoptional

4-digit EAS/ICD scheme for buyer_endpoint_id (e.g. 9930).

payment_termsstringoptional

EN 16931 BT-20 payment terms (BR-CO-25 for B2B).

supplier_legal_issuer_idintegeroptional

Host/supplier legal entity for a self-billed (389) invoice.

buyer_addressobjectoptional

{ line1, line2, city, region, postal_code, country_code }. country_code (BT-55) is required for a PEPPOL-valid invoice.

buyer_referencestringoptional

EN 16931 BT-10 buyer reference (PO / Leitweg-ID).

legal_issuer_idintegeroptional

Attach/replace the issuing legal entity (must belong to your org); re-freezes the invoice seller party.

Request
curl -X PATCH https://api.taxlens.getdynamiq.ai/v1/bookings/123/invoice-details \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TAXLENS_TOKEN" \
  -d '{
    "buyer_name": "ACME GmbH",
    "buyer_address": { "line1": "Hauptstrasse 1", "city": "Berlin", "postal_code": "10115", "country_code": "DE" },
    "buyer_reference": "PO-2026-7781"
  }'
Response
{
  "id": 123,
  "org_id": 7,
  "user_id": 8,
  "external_reference": "PMS-2026-00482",
  "idempotency_key": "pms-2026-00482-v1",
  "status": "confirmed",
  "total_tax": "45.00",
  "total_with_tax": "495.00",
  "currency": "EUR",
  "request": {
    "jurisdiction_code": "IT-62-ROM",
    "stay_date": "2026-06-01",
    "nightly_rate": 150,
    "currency": "EUR",
    "nights": 3,
    "property_type": "hotel"
  },
  "response": {
    "calculation_id": "calc_abc123",
    "jurisdiction": { "code": "IT-62-ROM", "name": "Roma", "path": "IT.62.ROM" },
    "tax_breakdown": { "components": [], "total_tax": "45.00", "effective_rate": "0.10", "currency": "EUR" },
    "total_with_tax": "495.00"
  },
  "metadata": {},
  "created_at": "2026-05-25T14:00:00Z",
  "updated_at": "2026-05-25T14:05:00Z",
  "voided_at": null
}
GET/v1/bookings/{booking_id}/document

Get Invoice Document Projection

Project a persisted booking as a country-agnostic JSON document aligned with EN 16931 / PEPPOL BIS Billing 3.0. Read-only. The `validation` block is a compliance pre-check: `is_valid` is true when the document is PEPPOL send-ready, otherwise `violations` lists the missing/invalid Business Terms (e.g. BT-44 buyer name). Used as the foundation for the UBL serializer and later CFDI / IRN / ZATCA ones. Returns 422 if the booking has no seller party — set up a legal issuer first via /v1/legal-issuers.

Parameters

booking_idintegerrequired

Booking ID.

Request
curl https://api.taxlens.getdynamiq.ai/v1/bookings/123/document \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
{
  "document": {
    "type_code": "380",
    "type_name": "Commercial invoice",
    "issue_date": "2026-05-25",
    "delivery_period": { "from": "2026-06-01", "to": "2026-06-04" },
    "currency": "EUR",
    "buyer_reference": null,
    "preceding_document_ref": null,
    "regulator_identifiers": {}
  },
  "seller": {
    "legal_name": "Acme Hotel SRL",
    "trading_name": "Acme Resort",
    "address": { "line1": "Via Roma 1", "city": "Roma", "postal_code": "00100", "country_code": "IT" },
    "tax_registrations": [
      { "scheme": "VAT", "scheme_icd": "9930", "value": "IT12345678901", "country_code": "IT" }
    ],
    "eas_endpoint": { "id": "IT12345678901", "scheme_code": "9930" }
  },
  "buyer": {
    "customer_type": "business", "tax_id": "DE111111111",
    "name": "ACME GmbH",
    "address": { "line1": "Hauptstrasse 1", "city": "Berlin", "postal_code": "10115", "country_code": "DE" }
  },
  "lines": [
    { "id": "1", "description": "Room rate", "item_type": "room",
      "quantity": "3", "unit_of_measure": "DAY",
      "unit_price": "150.00", "line_net": "450.00",
      "tax_category_code": "S", "tax_rate_percent": "10.00", "exemption_code": null }
  ],
  "tax_breakdown": [
    { "category_code": "S", "rate_percent": "10.00",
      "taxable_amount": "450.00", "tax_amount": "45.00", "exemption_code": null }
  ],
  "totals": {
    "lines_net_total": "450.00",
    "tax_total": "45.00",
    "grand_total": "495.00",
    "currency": "EUR",
    "amount_in_settlement_currency": null,
    "settlement_currency": null
  },
  "collection_info": {
    "merchant_of_record": "property",
    "platform_collected_components": [],
    "property_collected_components": ["accommodation_vat"]
  },
  "validation": { "is_valid": true, "violations": [] },
  "trace": {
    "booking_id": 123,
    "calculation_id": "calc_abc123",
    "data_version": "2026-05-24",
    "generated_at": "2026-05-25T14:00:00Z"
  }
}
GET/v1/bookings/{booking_id}/document/ubl

Export Invoice as UBL (PEPPOL BIS 3.0)

Serialize a booking's invoice document to UBL 2.1 XML conforming to PEPPOL BIS Billing 3.0 (the format that travels the PEPPOL network). Runs the EN 16931 pre-check first: if the booking is not send-ready it returns 422 with the exact list of violations (see example) instead of emitting an invoice a tax authority would reject. The export is VAT-only — occupancy/tourist taxes are not VAT and cannot share an EN 16931 invoice, so they are excluded and flagged as a warning on the JSON document. Supports MULTIPLE VAT rates on one invoice (e.g. room 10% + breakfast 20% → one cac:TaxSubtotal per rate) and EU cross-border B2B REVERSE CHARGE (category AE, 0% VAT, buyer VAT id + exemption reason — when a reverse-charge rule applies for a business buyer in a different country than the property). `cbc:ID` is a provisional identifier (BKG-{booking_id}) for this preview — issue the booking (POST /v1/bookings/{booking_id}/issue) to get the real fiscal number, after which this endpoint returns 409 and you use GET /v1/invoices/{invoice_id}/ubl. No Schematron/XSD, signing, or clearance yet. Response Content-Type is application/xml.

Parameters

booking_idintegerrequired

Booking ID.

Request
curl https://api.taxlens.getdynamiq.ai/v1/bookings/123/document/ubl \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
         xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
         xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
  <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
  <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
  <cbc:ID>BKG-123</cbc:ID>
  <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
  <!-- AccountingSupplierParty / AccountingCustomerParty / TaxTotal / ... -->
  <cac:LegalMonetaryTotal>
    <cbc:TaxInclusiveAmount currencyID="EUR">495.00</cbc:TaxInclusiveAmount>
    <cbc:PayableAmount currencyID="EUR">495.00</cbc:PayableAmount>
  </cac:LegalMonetaryTotal>
</Invoice>

# 422 when not send-ready:
{
  "detail": {
    "message": "Booking is not PEPPOL send-ready. Resolve the listed violations and try again.",
    "violations": [
      { "code": "BT-44", "severity": "error", "field": "BT-44", "message": "Buyer name is required. Pass buyer_name when creating the booking." }
    ]
  }
}
POST/v1/legal-issuers

Create Legal Issuer

Create a legal entity that issues invoices on behalf of this organization. One org can own many issuers (an OTA or PMS managing multiple properties, each its own legal seller). Admin or org owner only.

Request Body

legal_namestringrequired

Legal entity name.

trading_namestringoptional

DBA / display name.

country_codestringrequired

ISO 3166-1 alpha-2.

registered_addressobjectoptional

{line1, line2, city, region, postal_code, country_code}

default_currencystringoptional

ISO 4217.

eas_endpoint_idstringoptional

EN 16931 BT-34 PEPPOL electronic address.

eas_scheme_codestringoptional

ISO 6523 ICD (e.g. 9930 DE-VAT, 0088 GLN).

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/legal-issuers \
  -H "Authorization: Bearer $TAXLENS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "legal_name": "Acme Hotel SRL",
    "trading_name": "Acme Resort",
    "country_code": "IT",
    "registered_address": {"line1": "Via Roma 1", "city": "Roma", "postal_code": "00100", "country_code": "IT"},
    "default_currency": "EUR",
    "eas_endpoint_id": "IT12345678901",
    "eas_scheme_code": "9930"
  }'
Response
{
  "id": 1,
  "org_id": 7,
  "legal_name": "Acme Hotel SRL",
  "trading_name": "Acme Resort",
  "country_code": "IT",
  "registered_address": {"line1": "Via Roma 1", "city": "Roma", "postal_code": "00100", "country_code": "IT"},
  "default_currency": "EUR",
  "eas_endpoint_id": "IT12345678901",
  "eas_scheme_code": "9930",
  "is_active": true,
  "created_at": "2026-05-25T14:00:00Z",
  "updated_at": "2026-05-25T14:00:00Z"
}
POST/v1/legal-issuers/{issuer_id}/tax-registrations

Add Tax Registration

Attach a tax identifier (VAT, GSTIN, EIN, ABN, etc.) to a legal issuer. `scheme` and `country_code` are normalized to uppercase and the `(legal_issuer, country, scheme)` triple is unique.

Parameters

issuer_idintegerrequired

Legal issuer ID.

Request Body

country_codestringrequired

ISO 3166-1 alpha-2.

schemestringrequired

VAT, GSTIN, EIN, ABN, RFC, CUIT, CNPJ, UEN, etc.

scheme_icdstringoptional

ISO 6523 ICD.

valuestringrequired

The identifier value.

valid_fromdateoptional

ISO date.

valid_todateoptional

ISO date.

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/legal-issuers/1/tax-registrations \
  -H "Authorization: Bearer $TAXLENS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "country_code": "IT", "scheme": "VAT", "scheme_icd": "9930", "value": "IT12345678901" }'
Response
{
  "id": 1,
  "legal_issuer_id": 1,
  "country_code": "IT",
  "scheme": "VAT",
  "scheme_icd": "9930",
  "value": "IT12345678901",
  "valid_from": null,
  "valid_to": null,
  "created_at": "2026-05-25T14:00:00Z",
  "updated_at": "2026-05-25T14:00:00Z"
}
GET/v1/legal-issuers/{issuer_id}/tax-registrations

List Tax Registrations

List tax identifiers attached to a legal issuer. These feed the seller party in booking document projections and issued invoices.

Parameters

issuer_idintegerrequired

Legal issuer ID.

Request
curl https://api.taxlens.getdynamiq.ai/v1/legal-issuers/1/tax-registrations \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
{
  "items": [
    {
      "id": 1,
      "legal_issuer_id": 1,
      "country_code": "IT",
      "scheme": "VAT",
      "scheme_icd": "9930",
      "value": "IT12345678901",
      "valid_from": "2026-01-01",
      "valid_to": null,
      "created_at": "2026-05-25T14:00:00Z",
      "updated_at": "2026-05-25T14:00:00Z"
    }
  ]
}
DELETE/v1/legal-issuers/{issuer_id}/tax-registrations/{registration_id}

Delete Tax Registration

Remove one tax registration from a legal issuer. Existing booking snapshots and issued invoices remain frozen.

Parameters

issuer_idintegerrequired

Legal issuer ID.

registration_idintegerrequired

Tax registration ID.

Request
curl -X DELETE https://api.taxlens.getdynamiq.ai/v1/legal-issuers/1/tax-registrations/1 \
  -H "Authorization: Bearer $TAXLENS_TOKEN" -i
Response
HTTP/1.1 204 No Content

Invoice Reports

GET/v1/invoices/liability-summary

Invoice Tax Summary

Summarize issued-invoice tax liabilities by jurisdiction, authority, category, and currency. This is a read-only report derived from issued fiscal documents; calculated-but-unissued bookings are intentionally excluded. Credit notes reverse the original liability with negative amounts. Multi-currency reports return total_tax_amount as null and expose total_tax_amount_by_currency for remittance totals.

Parameters

fromdatetimeoptional

Inclusive issued_at lower bound.

todatetimeoptional

Inclusive issued_at upper bound.

currencystringoptional

ISO currency filter.

legal_issuer_idintegeroptional

Filter by org-owned legal issuer.

jurisdiction_codestringoptional

Exact component jurisdiction.

authoritystringoptional

Exact tax authority name.

category_codestringoptional

Tax category code.

is_testbooleanoptional

Set true for sandbox invoices, false for live invoices. If omitted, live invoices are returned unless include_test=true.

Request
curl "https://api.taxlens.getdynamiq.ai/v1/invoices/liability-summary?from=2026-06-01T00:00:00Z&to=2026-06-30T23:59:59Z" \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
{
  "items": [
    {
      "jurisdiction_code": "US-NY-NYC",
      "authority": "New York City Department of Finance",
      "category_code": "hotel_occupancy_tax",
      "currency": "USD",
      "taxable_amount": "750.0000",
      "tax_amount": "44.0600",
      "invoice_count": 3
    }
  ],
  "total_tax_amount": "44.0600",
  "total_tax_amount_by_currency": {
    "USD": "44.0600"
  }
}
GET/v1/invoices/liability-lines

Invoice Tax Lines

Return invoice-level component lines for liability reconciliation and remittance workpapers. Commercial invoices (380) and self-billed invoices (389) are positive; credit notes (381) are negative.

Parameters

limitintegeroptional

1-500 component lines.

offsetintegeroptional

Pagination offset.

jurisdiction_codestringoptional

Exact component jurisdiction.

authoritystringoptional

Exact authority name.

category_codestringoptional

Tax category code.

Request
curl "https://api.taxlens.getdynamiq.ai/v1/invoices/liability-lines?category_code=hotel_occupancy_tax" \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
{
  "items": [
    {
      "invoice_number": "2026-000001",
      "document_type": "380",
      "booking_id": 123,
      "property_id": 42,
      "jurisdiction_code": "US-NY-NYC",
      "authority": "New York City Department of Finance",
      "category_code": "hotel_occupancy_tax",
      "taxable_amount": "750.0000",
      "tax_amount": "44.0600",
      "sign": 1
    }
  ],
  "total": 1,
  "next_cursor": null
}
GET/v1/invoices/liability-export.csv

Export Invoice Tax CSV

Download the same invoice tax lines as CSV for remittance preparation and reconciliation.

No parameters
Request
curl "https://api.taxlens.getdynamiq.ai/v1/invoices/liability-export.csv?currency=USD" \
  -H "Authorization: Bearer $TAXLENS_TOKEN"
Response
invoice_number,document_type,booking_id,property_id,external_reference,issued_at,legal_issuer_id,currency,jurisdiction_code,authority,category_code,taxable_amount,tax_amount,sign
2026-000001,380,123,42,PMS-123,2026-06-04T12:00:00Z,7,USD,US-NY-NYC,New York City Department of Finance,hotel_occupancy_tax,750.0000,44.0600,1

Addresses

POST/v1/addresses/validate

Validate Address

Forward-geocode a free-text address and resolve it to a TaxLens jurisdiction code. Use this when you have a street address but no lat/lng or `jurisdiction_code` yet — the response can be fed directly into POST /v1/tax/calculate. The geocoder chain is LocationIQ primary with Nominatim fallback; sub-city resolution walks district → suburb → city → county → state, biased by `country_hint` when supplied. Returns 422 if the address cannot be geocoded at all. Note: a separate address-autocomplete / search endpoint is not yet available — this is the only address endpoint today.

Request Body

addressstringrequired

Free-text address (3-500 chars). The more specific, the better — "350 5th Ave, New York, NY" resolves higher-confidence than "New York".

country_hintstringoptional

ISO 3166-1 alpha-2 country code (e.g. "US", "HR"). Biases the geocoder toward that country and avoids ambiguity (e.g. Paris, France vs Paris, Texas).

Request
curl -X POST https://api.taxlens.getdynamiq.ai/v1/addresses/validate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-key>" \
  -d '{
    "address": "350 5th Ave, New York, NY 10118",
    "country_hint": "US"
  }'
Response
{
  "normalized_address": "Empire State Building, 350, 5th Avenue, Manhattan, New York County, City of New York, New York, 10118, United States",
  "lat": 40.748432,
  "lng": -73.985656,
  "country": "United States",
  "country_code": "US",
  "state": "New York",
  "city": "New York",
  "jurisdiction": {
    "code": "US-NY-NYC-MAN",
    "name": "Manhattan",
    "jurisdiction_type": "district",
    "path": "US.NY.NYC.MAN"
  },
  "ancestors": [
    { "code": "US", "name": "United States", "jurisdiction_type": "country", "path": "US" },
    { "code": "US-NY", "name": "New York", "jurisdiction_type": "state", "path": "US.NY" },
    { "code": "US-NY-NYC", "name": "New York City", "jurisdiction_type": "city", "path": "US.NY.NYC" }
  ],
  "confidence": "high"
}