Skip to content

Conventions

Times, money, paging, enums, idempotency and rate limits: the rules every endpoint follows.

Every endpoint follows the same rules. Learn them once and the API reference reads quickly.

Base URL and format#

  • Sandbox: https://api-dev-799b.up.railway.app/public/v1

  • Production: announced when it opens. Only the base URL and the key change.

  • Requests and answers are JSON (application/json), in UTF-8. The OAuth token endpoint is the one exception: it takes a form.

  • Fields are camelCase. A field with no value is null.

  • Unknown fields in a request body are refused (422), so typos don't pass silently.

  • Every answer has an X-Request-ID. Quote it when you contact us.

Times#

  • Times are RFC 3339 with an offset: 2026-11-20T21:00:00+08:00.
  • Answers are in the venue's time. Today every venue on Boozie is in Malaysia or Singapore, so the offset is always +08:00. Don't assume it: read the offset, or the venue's timeZone.
  • In requests, send any offset you like (Z works too). Boozie converts it to the venue's wall clock: 2026-11-20T13:00:00Z is 21:00 at the venue.
  • Dates (date, a guest-list night) are YYYY-MM-DD in venue time.
  • A night runs past midnight. A session from 00:00 to 03:00 belongs to the night before, as it does at a real venue: Friday's night includes Saturday 00:00–03:00. The API's day turns over at 06:00 venue time.

Money#

Money is an object with an amount in the currency's minor units and an ISO 4217 currency:

{ "amount": 30000, "currency": "MYR" }

That is RM 300.00. Never use floating point for money: keep the integer and format it for display. The venue's currency says what its amounts are in.

Paging#

Lists are cursor-paged:

curl "https://api-dev-799b.up.railway.app/public/v1/venues/7/bookings?limit=50" -H "Authorization: Bearer $BOOZIE_KEY"
{ "data": [{ "id": 812, "…": "…" }], "next": "eyJpZCI6ODEyfQ" }
  • limit is 50 by default and 200 at most.
  • Pass next as after to get the following page. next is null on the last page.
  • Treat the cursor as opaque: don't build or change one. A cursor that doesn't parse is 400 cursor.invalid.
  • To sync, list with updatedSince (and sort=updatedAt), keep the newest updatedAt you've seen, and use it next time. Better still, use webhooks.

Enums#

Enums are lowercase strings: a booking's status is one of pending, confirmed, rejected, cancelled or expired; its arrival is arrived, seated, no_show or null.

New values may be added without a new version. Treat a value you don't know as "other" rather than failing.

Idempotency#

Every POST that changes something needs an Idempotency-Key header: a unique string of 1–255 characters, such as a UUID.

curl -X POST "https://api-dev-799b.up.railway.app/public/v1/venues/7/bookings/812/confirm" \
  -H "Authorization: Bearer $BOOZIE_KEY" \
  -H "Idempotency-Key: 6f1c0e4e-1a0b-4c7e-9a43-2b7a3f1d9e10"
  • A retry with the same key and the same body within 24 hours gets the first answer again, with Idempotent-Replayed: true. Nothing is done twice.
  • The same key with a different body is 422 idempotency.mismatch. Use a new key for a new change.
  • While the first request is still running, a retry is 409 idempotency.in_progress with Retry-After: 1.
  • A server error (5xx) isn't kept, so a retry with the same key runs again.
  • A missing key is 400 idempotency.key_missing.

Make the key when you decide to make the change, store it with the change, and reuse it on every retry. PATCH and DELETE don't need a key, because sending them twice has the same effect as once.

Rate limits#

A key may make 60 requests a minute by default, with bursts of up to twice that (120). Some keys and apps have a higher limit; the headers on every answer show yours:

Header
RateLimit-LimitRequests a minute, e.g. 60
RateLimit-RemainingRequests you can send right now
RateLimit-ResetSeconds until the bucket is full again
RateLimit-Policye.g. 120;w=60;burst=120;rate=60
Retry-AfterOn a 429 only: seconds to wait

Over the limit the answer is 429 rate_limited. Wait Retry-After seconds, then retry. For anything else that fails with a 5xx or a network error, retry with exponential backoff and jitter, and keep the same Idempotency-Key.

Personal data#

Customers' names, phones and emails appear only for keys with the customers:pii scope, and in webhook payloads only when the endpoint has Include customers' contact details on. Ask for it only if you need it, and keep what you get no longer than you need it.

Next: Errors