Bookings API

A booking is a calculation made permanent — the immutable record you reconcile against and the thing an invoice is issued from. Create, list, get, adjust, void, and refund it from code.

Create a booking

POST /v1/bookings runs the calculation server-side (same engine as /v1/tax/calculate), freezes the request and response snapshots, and returns the booking record. Wrap the calculation payload in a calculation object and add booking-level fields alongside it. Bookings are org-scoped — the organization comes from your credential and can't be set by the caller.

FieldTypeDescription
calculationrequiredobjectA full TaxCalculationRequest. Also accepts buyer_name / buyer_address for a PEPPOL-valid invoice (these don't affect the tax).
idempotency_keystringClient-generated key (≤ 200 chars). Re-POSTing with the same key + body returns the existing booking with 200; a different body returns 409. See Errors & idempotency.
property_idintegerManaged property to inherit jurisdiction, tax drivers, and default issuers from. See Properties.
external_referencestringYour internal booking ID (≤ 200 chars) for list filters and reconciliation.
buyer_referencestringEN 16931 BT-10 buyer reference (PO number, XRechnung Leitweg-ID, French B2G reference; ≤ 200 chars). Invoice metadata — can also be set later via PATCH /v1/bookings/{id}/invoice-details.
buyer_endpoint_idstringBT-49 buyer electronic address (PEPPOL routing id; ≤ 200 chars) — needed for a transmission-ready B2B PEPPOL invoice. Can also be set later via PATCH /v1/bookings/{id}/invoice-details.
buyer_endpoint_schemestringEAS/ICD scheme code for buyer_endpoint_id (e.g. 9930; ≤ 20 chars). Can also be set later via PATCH /v1/bookings/{id}/invoice-details.
payment_termsstringBT-20 payment terms text (≤ 300 chars); defaults to 'Due on receipt' when omitted. Can also be set later via PATCH /v1/bookings/{id}/invoice-details.
legal_issuer_id / supplier_legal_issuer_idintegerThe invoice seller (and, for self-billed 389, the supplier). Explicit value wins, then the property default, then the org default.
is_testbooleanMark as a TEST booking so its invoices issue against an isolated TEST fiscal series. See Test vs live.
curl -X POST https://api.taxlens.getdynamiq.ai/v1/bookings \
  -H "X-API-Key: $TAXLENS_KEY" \
  -H "Content-Type: application/json" \
  -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"
    }
  }'
{
  "id": 4821,
  "org_id": 17,
  "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", "nightly_rate": 250.0, "nights": 3, "...": "..." },
  "response": { "calculation_id": "calc_97d7…", "tax_breakdown": { "...": "..." } },
  "metadata": {},
  "created_at": "2026-05-21T10:29:52.943980Z",
  "voided_at": null
}

List & get

GET /v1/bookings returns your org's bookings, newest first, with cursor-based pagination — pass the previous next_cursor back to page on, and stop when it's null. Filter by status (confirmed, adjusted, voided, refund), external_reference, and a from_date/to_date creation range. GET /v1/bookings/{id} fetches one, including the frozen request snapshot and the persisted calculation. Bookings from another org return 404, never 403.

curl "https://api.taxlens.getdynamiq.ai/v1/bookings?status=confirmed&from_date=2026-05-01T00:00:00Z&limit=25" \
  -H "X-API-Key: $TAXLENS_KEY"

curl https://api.taxlens.getdynamiq.ai/v1/bookings/4821 \
  -H "X-API-Key: $TAXLENS_KEY"
GET/v1/bookings
Sign in to run

List your organization's recent bookings (read-only).

Sign in to run this against the live API. Read-only — nothing is saved.

Adjust (re-runs the engine)

When a tax-affecting input changes — dates, jurisdiction, nightly price, guest count, property type, buyer context — PATCH /v1/bookings/{id} re-runs the engine. Send a complete replacement calculation, not just the changed fields. The same row stays, status becomes adjusted, totals and snapshots are replaced, and the prior calculation is appended to metadata.adjustment_history.

curl -X PATCH https://api.taxlens.getdynamiq.ai/v1/bookings/4821 \
  -H "X-API-Key: $TAXLENS_KEY" \
  -H "Content-Type: application/json" \
  -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,
      "currency": "USD",
      "nights": 3,
      "number_of_guests": 2,
      "property_type": "hotel"
    }
  }'

Edit invoice details (no re-calc)

To set or correct invoice-only metadata — buyer name and address, buyer reference, PEPPOL endpoint, payment terms, or the attached legal issuer — use PATCH /v1/bookings/{id}/invoice-details. This does not re-run the engine and does not append to adjustment_history; it's distinct from adjust. Use it to make a booking send-ready before issuing.

curl -X PATCH https://api.taxlens.getdynamiq.ai/v1/bookings/4821/invoice-details \
  -H "X-API-Key: $TAXLENS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "buyer_name": "ACME GmbH",
    "buyer_address": { "line1": "Hauptstrasse 1", "city": "Berlin", "postal_code": "10115", "country_code": "DE" },
    "buyer_endpoint_id": "DE123456789",
    "buyer_endpoint_scheme": "9930"
  }'

Void & refund

DELETE /v1/bookings/{id} voids a booking. Voids are sticky and idempotent — the row isn't deleted, status flips to voided and voided_at is set, and a second DELETE is a no-op (returns 204).

POST /v1/bookings/{id}/refund processes a partial or full refund and returns a per-jurisdiction tax_delta — the proportional fraction of the originally-collected tax to return. It's idempotent via refund_idempotency_key. A full refund (refund_amount ≥ total_with_tax) flips status to refund; a partial refund preserves the prior status.

FieldTypeDescription
refund_amountrequireddecimalIn booking currency, > 0 and ≤ total_with_tax.
refund_currencyrequiredstringISO 4217. Must match the booking currency.
refund_reasonrequiredstringguest_cancel | courtesy | no_show | duplicate_charge | downgrade | other.
refund_idempotency_keystringRe-POSTing with the same key returns the prior tax delta (≤ 200 chars).
curl -X POST https://api.taxlens.getdynamiq.ai/v1/bookings/4821/refund \
  -H "X-API-Key: $TAXLENS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "refund_amount": 100.00,
    "refund_currency": "USD",
    "refund_reason": "guest_cancel",
    "refund_idempotency_key": "refund-4821-v1"
  }'
A live invoice freezes the booking
Once a live issued invoice (380/389) exists for a booking, adjust, void, refund, and invoice-details all return 409. Corrections then go through a credit note. See E-invoicing API and Numbering & immutability.

Next

Turn a send-ready booking into a fiscal document in E-invoicing API, and see how the dashboard surfaces the same lifecycle in Bookings.