Errors & idempotency

How the API signals failure, how to read a validation error, and how idempotency keys make booking writes safe to retry.

The error shape

Errors come back as JSON with a top-level detail field. For most failures detail is a plain string you can show or log directly:

{
  "detail": "Booking not found"
}

For request-schema problems (HTTP 422), detail is an array of validation issues. Each entry has a loc (the path to the offending field, where the first element is usually "body"), a msg, and a type:

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "stay_date"],
      "msg": "Field required"
    },
    {
      "type": "greater_than",
      "loc": ["body", "nightly_rate"],
      "msg": "Input should be greater than 0"
    }
  ]
}
Tip
A robust client flattens each item to field: message — join the loc parts (dropping the leading "body") with the msg. That is exactly what the TaxLens dashboard does, turning the array into a readable line like nightly_rate: Input should be greater than 0 instead of [object Object].

Status codes you'll see

FieldTypeDescription
400Bad RequestA semantically invalid request the engine rejects at runtime — e.g. an unknown jurisdiction, missing coordinates, or a currency mismatch on a refund.
401UnauthorizedMissing, malformed, or revoked credential. The Authorization header was absent or the token/key is no longer valid.
404Not FoundThe resource doesn't exist — or belongs to another organization. Cross-org reads return 404 (never 403) so existence isn't leaked.
409ConflictA state conflict: an idempotency key reused with a different body, or a mutation blocked because a live invoice has frozen the booking (adjust / void / refund / invoice-details all 409).
422Unprocessable EntityRequest body failed schema validation (the array shape above) — including a cross-field check like a checkout_date that doesn't equal stay_date + nights — or an e-invoice isn't send-ready when you request its UBL.
A 401 from a session expires the dashboard
When the in-app client receives a 401 on any non-auth endpoint it clears the stored session token and signs the user out — a session-expiry safety net. A server-side integration using an API key should instead surface the 401, check the key, and stop retrying (a bad credential won't fix itself).

Idempotency on bookings

Network retries are a fact of life, and you don't want a timeout to double-charge tax or create a duplicate booking. Pass an idempotency_key (your own client-generated string, up to 200 chars) when you create a booking. The key is unique per organization.

FieldTypeDescription
Same key + same body→ 200Returns the original booking row that the first request created. The first successful write returns 201; every replay returns the same record with 200. Safe to retry indefinitely.
Same key + different body→ 409A conflict. The key was already used for a different calculation — the server refuses to silently overwrite or fork. Use a fresh key for a genuinely new booking.
No key→ 201 each timeWithout a key, every POST creates a new booking. Fine for one-shot calls; risky on a flaky connection.
# Re-running this exact request returns the same booking (200), not a duplicate.
curl -X POST https://api.taxlens.getdynamiq.ai/v1/bookings \
  -H "X-API-Key: $TAXLENS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "pms-2026-00482-v1",
    "external_reference": "PMS-2026-00482",
    "calculation": {
      "jurisdiction_code": "US-NY-NYC",
      "stay_date": "2026-06-15",
      "nightly_rate": 250,
      "currency": "USD",
      "nights": 3
    }
  }'
Detail
Refunds have their own idempotency lever — refund_idempotency_key on POST /v1/bookings/{id}/refund — so a retried refund returns the prior tax delta instead of clawing back twice. See Bookings API.

The request log

Billable endpoints — /v1/tax/calculate, /v1/addresses/validate, and the /v1/bookings/* routes — are recorded to your organization's request log. Each entry captures the endpoint, status, and a redacted copy of the payload (sensitive keys are scrubbed; a malformed non-JSON body is stored only as a byte count, never raw). The log is your audit trail for reconciliation and usage. It is API-only — fetch it from GET /v1/orgs/me/request-log (organization admins only); there is no dashboard screen for it.

Next

Ready to call the engine? Calculate tax walks the full request and response. For lifecycle writes that lean on idempotency, see Bookings API.