Overview
V1-compatible external API
The external API is available to merchants whose plan enables external API access and whose account is in good standing. Every request uses a bearer API key plus the merchant ID header.
auth
Authentication
Requests must use a bearer API key and the merchant ID header. The same key is bound to exactly one merchant.
Headers
Authorization: Bearer buqq_live_...
X-Merchant-Id: 550e8400-e29b-41d4-a716-446655440000| Property | Requirement | Notes |
|---|---|---|
| Authorization | Required | Bearer <api_key>. Reject missing/invalid/revoked keys with INVALID_API_KEY. |
| X-Merchant-Id | Required | Merchant UUID. Must match the key's merchant or MERCHANT_MISMATCH is returned. |
| Idempotency-Key | POST /bookings only | Required for booking creation. Reusing a key with a different body returns IDEMPOTENCY_CONFLICT. |
errors
Errors
All error codes returned by the external API. Auth errors run before business logic and apply to every endpoint.
| Code | Status | Endpoint | Description |
|---|---|---|---|
| INVALID_API_KEY | 401 | All endpoints | Missing, invalid, or revoked API key or X-Merchant-Id header. |
| MERCHANT_MISMATCH | 403 | All endpoints | API key does not belong to the merchant in X-Merchant-Id. |
| PLAN_NOT_ENTITLED | 423 | All endpoints | Merchant plan does not include external API access. |
| BILLING_SUSPENDED | 423 | All endpoints | Merchant billing is suspended. |
| INTERNAL_ERROR | 500 | All endpoints | Unexpected server failure. |
| VALIDATION_ERROR | 400 | All endpoints | Request body or query params failed validation. Includes a field-level errors array. |
| MISSING_IDEMPOTENCY_KEY | 400 | POST /bookings | Idempotency-Key header missing. |
| CONFLICT | 409 | POST /bookings | Idempotency-Key was already used with a different request body. |
| IDEMPOTENCY_CONFLICT | 409 | POST /bookings | Same Idempotency-Key is already being processed - retry in a few seconds. |
| ADVANCE_TIME_REQUIRED | 400 | POST /bookings | Booking time is within the restaurant's minimum advance window. |
| BOOKING_LIMIT_REACHED | 403 | POST /bookings | Monthly booking cap for this restaurant has been reached. |
| CAPACITY_EXCEEDED | 400 | POST /bookings | Requested slot does not have sufficient capacity for this party size. |
| RESTAURANT_UNAVAILABLE | 400 | POST /bookings | Restaurant is closed or not accepting bookings at the requested time. |
| DUPLICATE_BOOKING | 409 | POST /bookings | A booking with the same guest contact (email or phone), time, and party size was created in the last 5 minutes. |
| NOT_FOUND | 404 | PATCH /bookings/:id/status | Booking does not exist. |
availability
GET /api/external/v1/availability
Returns available start times for a restaurant on a specific date.
| Property | Requirement | Notes |
|---|---|---|
| date | Required query param | YYYY-MM-DD local restaurant date. |
| partySize | Required query param | Integer >= 1. Used for capacity calculations. Must be within the restaurant's minPartySize–maxPartySize range to find available slots. |
Example request (cURL)
curl -X GET "https://buqq.online/api/external/v1/availability?date=2026-05-18&partySize=4" \
-H "Authorization: Bearer buqq_live_..." \
-H "X-Merchant-Id: 550e8400-e29b-41d4-a716-446655440000"Example request (JavaScript)
const res = await fetch(
`https://buqq.online/api/external/v1/availability?date=2026-05-18&partySize=4`,
{
headers: {
"Authorization": "Bearer buqq_live_...",
"X-Merchant-Id": "550e8400-e29b-41d4-a716-446655440000",
},
}
);
const { availableSlots } = await res.json();Success response 200
{
"availableSlots": ["18:00", "19:00", "20:00"],
"fullSlots": ["17:00", "17:30"],
"timezone": "Asia/Bangkok",
"minPartySize": 1,
"maxPartySize": 10
}| Property | Requirement | Notes |
|---|---|---|
| availableSlots | string[] | HH:MM start times available for the requested party size on that date. |
| fullSlots | string[] | HH:MM slots that exist on this date but are at full capacity for the requested party size. Useful for showing greyed-out times in a picker. |
| timezone | string | IANA timezone identifier for the restaurant (e.g. Asia/Bangkok). |
| minPartySize | integer | Smallest party size the restaurant accepts. Use this to validate booking form inputs before calling POST /bookings. |
| maxPartySize | integer | Largest party size the restaurant accepts. Use this to validate booking form inputs before calling POST /bookings. |
bookings get
GET /api/external/v1/bookings
Lists bookings for a merchant across a date range with cursor pagination.
| Property | Requirement | Notes |
|---|---|---|
| startDate | Required query param | YYYY-MM-DD, inclusive start of the date range. |
| endDate | Required query param | YYYY-MM-DD, inclusive end of the date range. |
| status | Optional query param | Comma-separated statuses: pending, checked-in, cancelled, no-show. |
| cursor | Optional query param | Last booking id from the previous page. |
| limit | Optional query param | Default 50, max 200. |
Example request (cURL)
curl -X GET "https://buqq.online/api/external/v1/bookings?startDate=2026-05-18&endDate=2026-05-18" \
-H "Authorization: Bearer buqq_live_..." \
-H "X-Merchant-Id: 550e8400-e29b-41d4-a716-446655440000"Example request (JavaScript)
const res = await fetch(
`https://buqq.online/api/external/v1/bookings?startDate=2026-05-18&endDate=2026-05-18`,
{
headers: {
"Authorization": "Bearer buqq_live_...",
"X-Merchant-Id": "550e8400-e29b-41d4-a716-446655440000",
},
}
);
const { bookings, pagination } = await res.json();Success response 200
{
"bookings": [
{
"id": "4c4a04e4-3e3d-4db7-b6f5-0e2e6d5bb7d4",
"bookingRef": "PW-1001",
"customerName": "Jane Doe",
"phone": "+85512345678",
"email": "jane@example.com",
"partySize": 4,
"bookingTimeUtc": "2026-05-18T11:00:00.000Z",
"localDate": "2026-05-18",
"localTime": "18:00",
"status": "pending",
"source": "pos",
"occasionType": "birthday",
"occasionNote": "Bring a small cake",
"specialNotes": "Window table if available",
"createdAt": "2026-05-11T04:12:33.123Z"
}
],
"pagination": {
"nextCursor": "4c4a04e4-3e3d-4db7-b6f5-0e2e6d5bb7d4",
"hasMore": true,
"limit": 50,
"total": 1
}
}bookings post
POST /api/external/v1/bookings
Creates a booking. Idempotency is required and duplicate submissions are guarded by the API and booking engine.
| Property | Requirement | Notes |
|---|---|---|
| customerName | Required body field | 2-100 characters. |
| At least one of email / phone required | Valid email address. Omit if phone is provided. | |
| phone | At least one of email / phone required | Digits with optional leading +, 8-15 digits total. Omit if email is provided. |
| partySize | Required body field | Integer >= 1. |
| localDate | Required body field | YYYY-MM-DD in the restaurant's local timezone. |
| localTime | Required body field | HH:MM using 15-minute intervals (e.g. 18:00, 18:15, 18:30, 18:45). Must match a slot from the availability endpoint. |
| notes | Optional body field | Free text special requests. |
| occasionType | Optional body field | birthday, anniversary, date_night, business, family, celebration, other. |
| occasionNote | Optional body field | Short occasion note. |
Example request (cURL)
curl -X POST "https://buqq.online/api/external/v1/bookings" \
-H "Authorization: Bearer buqq_live_..." \
-H "X-Merchant-Id: 550e8400-e29b-41d4-a716-446655440000" \
-H "Idempotency-Key: 6b2d3e1e-97ef-4c8e-8d87-0e5d4d9e32ab" \
-H "Content-Type: application/json" \
-d '{
"customerName": "Jane Doe",
"phone": "+85512345678",
"email": "jane@example.com",
"partySize": 4,
"localDate": "2026-05-18",
"localTime": "18:00",
"notes": "Window table if available",
"occasionType": "birthday",
"occasionNote": "Bring a small cake"
}'Example request (JavaScript)
const res = await fetch(`https://buqq.online/api/external/v1/bookings`, {
method: "POST",
headers: {
"Authorization": "Bearer buqq_live_...",
"X-Merchant-Id": "550e8400-e29b-41d4-a716-446655440000",
"Idempotency-Key": "6b2d3e1e-97ef-4c8e-8d87-0e5d4d9e32ab",
"Content-Type": "application/json",
},
body: JSON.stringify({
customerName: "Jane Doe",
phone: "+85512345678",
email: "jane@example.com",
partySize: 4,
localDate: "2026-05-18",
localTime: "18:00",
notes: "Window table if available",
}),
});
const booking = await res.json();Success response 201
{
"id": "4c4a04e4-3e3d-4db7-b6f5-0e2e6d5bb7d4",
"bookingRef": "PW-1001",
"customerName": "Jane Doe",
"phone": "+85512345678",
"email": "jane@example.com",
"partySize": 4,
"bookingTimeUtc": "2026-05-18T11:00:00.000Z",
"localDate": "2026-05-18",
"localTime": "18:00",
"status": "pending",
"source": "pos",
"occasionType": "birthday",
"occasionNote": "Bring a small cake",
"specialNotes": "Window table if available",
"createdAt": "2026-05-11T04:12:33.123Z"
}bookings patch
PATCH /api/external/v1/bookings/:bookingIdentifier/status
Updates the reservation status using either the UUID id or the short bookingRef.
| Property | Requirement | Notes |
|---|---|---|
| bookingIdentifier | Path param | UUID reservation id or short bookingRef. |
| status | Required body field | pending, checked-in, cancelled, or no-show. |
Example request (cURL)
curl -X PATCH "https://buqq.online/api/external/v1/bookings/PW-1001/status" \
-H "Authorization: Bearer buqq_live_..." \
-H "X-Merchant-Id: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{ "status": "checked-in" }'Example request (JavaScript)
const res = await fetch(
`https://buqq.online/api/external/v1/bookings/PW-1001/status`,
{
method: "PATCH",
headers: {
"Authorization": "Bearer buqq_live_...",
"X-Merchant-Id": "550e8400-e29b-41d4-a716-446655440000",
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "checked-in" }),
}
);Success response 200
{
"id": "4c4a04e4-3e3d-4db7-b6f5-0e2e6d5bb7d4",
"bookingRef": "PW-1001",
"customerName": "Jane Doe",
"phone": "+85512345678",
"email": "jane@example.com",
"partySize": 4,
"bookingTimeUtc": "2026-05-18T11:00:00.000Z",
"status": "checked-in",
"source": "pos",
"occasionType": "birthday",
"occasionNote": "Bring a small cake",
"specialNotes": "Window table if available",
"createdAt": "2026-05-11T04:12:33.123Z"
}merchant keys
API key management
API keys are managed entirely from your Buqq merchant dashboard — no API calls required.
To generate, rotate, or revoke your API key, visit the Integrations page in your merchant dashboard. One active key is allowed per merchant at any time. Rotating a key immediately revokes the previous one, so update any connected systems before rotating.
Your Merchant ID (needed for the X-Merchant-Id header) is also shown on that page.
rules
Lifecycle rules
These rules should stay stable across docs, UI, and backend enforcement.
One active API key per merchant.
Refreshing a key rotates it: revoke the old key, mint a new one.
Revoked keys cannot authenticate.
Successful API use updates last_used_at.
Plan entitlement and billing status are checked before access is granted.
Booking creation must be idempotent.
At least one of email or phone is required per booking — both are optional when the other is present.
Duplicate guest/time/party-size bookings within 5 minutes are rejected.
The docs page must describe the backend contract exactly, not just the marketing version.
idempotency
Idempotency
Booking creation is idempotent. Two independent guards prevent duplicate reservations.
| Property | Requirement | Notes |
|---|---|---|
| Idempotency-Key header | Required on POST /bookings | A UUID you generate per booking attempt. Include it in every create request. |
| 24-hour dedup window | Behaviour | Replaying the same Idempotency-Key with the same request body within 24 hours returns the original 201 response without creating a new booking. |
| IDEMPOTENCY_CONFLICT (409) | Error | The same Idempotency-Key was reused with a different request body. Generate a new key for a different booking. |
| DUPLICATE_BOOKING (409) | Error | A separate 5-minute guard rejects any booking with the same email + time + party size, regardless of idempotency key. Protects against two different callers creating logically identical bookings simultaneously. |
Recommended pattern
// Generate a stable key per booking attempt (store it if you need to retry)
const idempotencyKey = crypto.randomUUID();
const res = await fetch(`${BASE_URL}/api/external/v1/bookings`, {
method: "POST",
headers: {
"Authorization": "Bearer buqq_live_...",
"X-Merchant-Id": "YOUR_MERCHANT_ID",
"Idempotency-Key": idempotencyKey, // same key on retry
"Content-Type": "application/json",
},
body: JSON.stringify({ /* booking fields */ }),
});
// On network failure: retry with the SAME idempotencyKey
// On success: discard the key — generate a new one for the next bookingchangelog
Changelog
API version history. The external API uses date-based versioning via the URL path.
| Version | Date | Notes |
|---|---|---|
| v1.2 | June 2026 | GET /availability response now includes fullSlots — time slots that exist on the date but are at capacity for the requested party size. Backwards compatible additive field. |
| v1.1 | June 2026 | email and phone on POST /bookings are now independently optional — at least one must be provided. Previously both were required. Backwards compatible: existing callers sending both fields are unaffected. |
| v1.0 | April 2026 | Initial release. Availability check, bookings list, booking creation with idempotency, booking status update, and API key lifecycle management. |