Skip to main content

Conventions

The conventions below apply to every endpoint in the Operations API. They are modelled on the Stripe API so integrations feel familiar and predictable.

Authentication​

Requests authenticate with a site-scoped API key in the Authorization header:

Authorization: Bearer bf_live_51H8...
  • bf_live_... keys operate on production data; bf_test_... keys are for the test environment.
  • Keys are issued and revoked in the BookingFlow dashboard. Only the SHA-256 hash is stored server-side; revocation takes effect immediately.
  • A request with a missing key returns 401 with "code": "missing_api_key"; an invalid key returns "code": "invalid_api_key".
  • Every authenticated request resolves to exactly one site — the site the key belongs to. You never pass a site id in the URL; keys cannot read or write another park's data.

App Check is not used on the API (it protects the Firebase client apps, not server-to-server integrations).

Versioning​

  • The major version is in the path: /v1/....
  • Additive changes (new optional fields, new endpoints, new event types) do not bump the version — code that tolerates unknown fields is future-proof.
  • Breaking changes ship as /v2 alongside /v1 with a deprecation window.
  • Responses carry BookingFlow-Version: v1 for debugging.

Data types​

TypeRepresentation
TimestampsUnix epoch seconds (integers), e.g. 1735689600. Requests accept Unix seconds or ISO 8601.
MoneyIntegers in minor units (pence), e.g. 12345 = £123.45, with a lowercase currency code: "gbp".
Calendar datesISO YYYY-MM-DD (used in rates and invoice periods, where a park thinks in nights, not instants).
IdsOpaque strings (Firestore document ids). Treat them as case-sensitive cookies.

Errors​

Every failure returns a single envelope with an appropriate HTTP status:

{
"error": {
"type": "invalid_request_error",
"code": "booking_unavailable",
"message": "This accommodation is not available for the selected dates.",
"param": "accommodation",
"doc_url": "https://docs.bookingflow.app/api/bookings"
}
}
typeHTTPMeaning
invalid_request_error400Invalid input, failed validation, or an unmet precondition
authentication_error401Missing, invalid, or revoked API key
permission_error403The key may not perform this operation
resource_missing404The referenced object does not exist (or belongs to another site)
idempotency_error409 / 422Idempotency conflict (in progress, or key reused with a different body)
api_error500Unexpected server error — safe to retry

code is stable and machine-readable (see the resource reference for the codes each endpoint returns); param names the offending field when relevant; doc_url links to the relevant docs page.

Idempotency​

All POST endpoints honour the Idempotency-Key header:

  • Send a unique key per logical operation (a UUID is fine; a stable name like meter-round-2026-11-30 is better for agents).
  • The first execution stores the response; replays with the same key and the same request body return the stored response with an Idempotent-Replayed: true header — the handler does not run again.
  • The same key with a different body returns 422 (idempotency_key_reused).
  • If a request with the key is still in flight, a concurrent duplicate gets 409 — retry the same request shortly.
  • Keys expire after 24 hours. Claims on crashed executions expire after 5 minutes. Responses with status ≥ 500 are never stored — the next call re-executes.

Idempotency is also guaranteed where it matters semantically: invoice generation never creates a second invoice for the same closing meter reading, even without a key.

Pagination​

List endpoints use cursor pagination:

GET /v1/bookings?limit=25
{
"object": "list",
"url": "/v1/bookings",
"has_more": true,
"data": [ ... ]
}
  • limit — 1 to 100 (default 25).
  • starting_after — the id of the last object in the previous page.
  • ending_before — the id of the first object in the next page, for paging backwards.
  • has_more tells you whether another page exists. Bookings list newest records in document-id order; events and meter readings list newest-first.

Expansion​

Retrieval endpoints that return related ids accept expand[]:

GET /v1/bookings/bk_123?expand[]=accommodation&expand[]=customer

The accommodation and customer fields are replaced with their full objects inline. Unexpanded, they are plain ids. (Only booking accommodation and customer are expandable in v1.)

Metadata​

Attach up to 50 key/value string pairs to bookings, customers, accommodations, pitches, meter readings, rates and webhook endpoints:

{ "metadata": { "tour": "spring-rally", "segment": "returning" } }
  • Keys ≤ 40 chars; values ≤ 500 chars. Numbers and booleans are coerced to strings.
  • PATCH merges metadata (DELETE of a single key is not supported in v1 — send the full metadata object you want stored).

Webhooks​

Register HTTPS endpoints via Webhook endpoints; every mutation (from the API or the dashboards' shared core logic where wired) emits an event that is POSTed to each enabled endpoint with an HMAC signature:

BookingFlow-Signature: t=1735689600,v1=5257a869e7...

The signature is HMAC-SHA256 over "{t}.{payload}" using the endpoint's whsec_... secret (returned once at creation). Verify by recomputing and comparing, and reject timestamps older than five minutes. Delivery is at-least-once with one attempt per event in v1; record deliveries are visible on the event objects.

Rate limits​

Not enforced in v1 — the API is currently sized for operational (not public-traffic) volumes. Abusive callers can be throttled at the key level; contact support if you need sustained high volume.

Agent notes​

The API is deliberately friendly to autonomous callers:

  • Send Idempotency-Key on every write; derive it from your intent (for example create-booking-{plan-id}), so retries and replays are free.
  • Bulk endpoints (meter_readings/bulk, invoices/generate_all, rates/bulk) report per-row outcomes and never fail an entire batch because one row is bad. Fix the reported rows and re-send only those.
  • Errors carry code + param + message; use them to self-correct.
  • GET /v1 confirms your key, its site, and its mode before a long session.