Authentication & errors
How a request proves who it is, and how failures are reported.
Last updated
Base URL and format#
Every path in these docs is relative to the production base URL:
https://myescaperoom.eu- Requests and responses are JSON. Send
Content-Type: application/jsonon a request with a body. - Money is in integer cents (
amountCents: 10000is 10,000 cents, 100.00 euros), with acurrencywhere it matters. - Instants (
start,createdAt) are ISO 8601 strings in UTC, such as2026-11-02T17:00:00.000Z. Show them in the venue's time zone, which the venue object gives astimezone. - A day (
date,from,toon availability) is a plainYYYY-MM-DDin the venue's time zone.
Three ways to call the API#
| How | Used by | Proves |
|---|---|---|
| None (public) | Discovery, public room pages, the quote endpoint, leaderboards | Nothing. Anyone may call it. |
| Venue API key | Every /api/embed/* endpoint. This is what an integration uses. | That the caller speaks for one venue, with the permissions the venue gave that key. |
| Session cookie | The player portal (/api/me/*) and the dashboard (/api/staff/*) | A signed-in person: a player, or a member of a venue's staff. |
Each endpoint in the API reference is labelled Public, Session or API key.
Venue API keys#
A key is created by an owner or admin on the dashboard's API page, see API keys. Send it on every Embed API request, as a bearer token or in x-api-key. If both headers are present, the Authorization bearer token is used.
curl https://myescaperoom.eu/api/embed/rooms \
-H "Authorization: Bearer $MER_API_KEY"
# The same key in the other accepted header:
curl https://myescaperoom.eu/api/embed/rooms \
-H "x-api-key: $MER_API_KEY"- A key is tied to one venue. Anything of another venue is answered as not found (
404), never as forbidden, so ids cannot be probed. - The venue must be live. A key whose venue is in draft, in review or suspended is refused exactly like an unknown key.
- A missing, unknown, revoked or unusable key is always
401 UNAUTHORIZED. The answer deliberately does not say which. - Three permissions can be switched on per key: May manage vouchers, May cancel after the player deadline and May read guest details. A call that needs one of the first two and the key lacks it is
403 API_KEY_NOT_PERMITTED. Without the third, the guests' name, email and phone arenullin every booking answer, and the buyer of a voucher isnullin voucher answers (unless the key may manage vouchers).
Calling from a browser (CORS)#
Calls from your server need nothing else. Calls from a web page also need the page's origin on the venue's Allowed origins list. The Embed API then answers with the matching Access-Control-Allow-Origin. A request that carries any other Origin is refused with 403 ORIGIN_NOT_ALLOWED before the API does anything with it, on every Embed route and method: a booking is not created, whatever the browser lets the page read. A venue with no allowed origins accepts no browser from another site. A call with no Origin header is not affected. The preflight OPTIONS request is answered as before.
OPTIONS /api/embed/bookings
→ 204
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, x-api-key
Access-Control-Max-Age: 86400
POST /api/embed/bookings (Origin: https://www.your-venue.com, an allowed origin)
→ Access-Control-Allow-Origin: https://www.your-venue.com
Access-Control-Allow-Credentials: false
Vary: Origin
POST /api/embed/bookings (Origin: https://other-site.example, not allowed)
→ 403 { "code": "ORIGIN_NOT_ALLOWED" }
no Access-Control-Allow-Origin, and no booking was created- Only
GET,POSTandOPTIONSare allowed from a browser, and only the headersContent-Type,Authorizationandx-api-key.PUT /api/embed/bookings/{ref}/invoice-requesttherefore has to be called from your server. - Credentials are not allowed (
Access-Control-Allow-Credentials: false): never rely on cookies for the Embed API. - A
401or anORIGIN_NOT_ALLOWEDlacks theAccess-Control-Allow-Originheader, so in a browser a bad key or an origin that is not listed shows up as a CORS error, not as the JSON body. - The public quote endpoint,
POST /api/bookings/quote, has no CORS headers. Call it from your server.
Session cookie#
Players and venue staff sign in with email and password and get a session cookie. A venue integration does not need one for the Embed API. You meet it in two places:
- The player portal and dashboard call their own routes (
/api/me/*,/api/staff/*) with it. Those are the same calls the websites make and are listed in the reference with a Session label. - Logging a session's escape time from your own system has no API key yet, so it signs in as a staff member. See Logging escape times.
The player, staff and operator sessions are three separate realms. The same email address can exist in more than one, as unrelated accounts, and a cookie of one never works in another.
Errors#
A failure is a non-2xx status and a JSON body with a code:
{ "code": "SLOT_UNAVAILABLE" }When a body or query was refused as invalid, the code is INVALID_INPUT and issues lists one entry per bad field, all at once, with its path and a message:
{
"code": "INVALID_INPUT",
"issues": [
{ "code": "invalid_format", "path": ["guestEmail"], "message": "Invalid email address" },
{ "code": "too_small", "path": ["teamSize"], "message": "Too small: expected number to be >0" }
]
}Branch on the code, never on the message text or the status alone. Codes are stable, messages can change, and several codes share a status.
const res = await fetch("https://myescaperoom.eu/api/embed/bookings", {
method: "POST",
headers: {
Authorization: "Bearer " + YOUR_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
const body = await res.json();
if (!res.ok) {
// Branch on body.code, never on the message or on the status alone.
switch (body.code) {
case "SLOT_UNAVAILABLE":
// Someone else took the session: offer another one.
break;
case "INVALID_INPUT":
// body.issues lists every bad field at once.
break;
case "STRIPE_ERROR":
// The hold was released; it is safe to try again shortly.
break;
default:
throw new Error("MyEscapeRoom refused the request: " + body.code);
}
}Statuses and their codes#
| Status | Meaning | Codes you may see |
|---|---|---|
400 | The request itself was refused: not JSON, or a field is missing or out of range. | INVALID_JSON, INVALID_INPUT, PHONE_REQUIRED, PHONE_INVALID |
401 | No key, an unknown or revoked key, or a venue that is not live. | UNAUTHORIZED |
403 | The key is valid but may not do this, or the request came from a browser on a site the venue did not allow. | API_KEY_NOT_PERMITTED, CANCELLATION_NOT_ALLOWED, ORIGIN_NOT_ALLOWED |
404 | Nothing there, or it belongs to another venue. | SLOT_NOT_FOUND, ROOM_NOT_FOUND, BOOKING_NOT_FOUND, VOUCHER_NOT_FOUND, INVOICE_REQUEST_NOT_FOUND |
409 | The state of the session, booking or voucher forbids it. | SLOT_UNAVAILABLE, SLOT_IN_PAST, VENUE_UNAVAILABLE, DISCOUNT_EXHAUSTED, VOUCHER_EMPTY, VOUCHER_PAID, VOUCHER_IN_USE, VOUCHER_CANCELLED, PAYMENT_PROCESSING, BOOKING_PAID, BOOKING_PLAYED, GAME_STARTED, CANCELLATION_DEADLINE_PASSED, INVOICE_ALREADY_REQUESTED, INVOICE_ALREADY_ISSUED |
422 | Well formed, but it cannot be done as asked. | INVALID_TEAM_SIZE, PAYMENT_MODE_NOT_ALLOWED, CONTACT_PRICING, DISCOUNT_INVALID, VOUCHER_INVALID, INVOICE_NOT_AVAILABLE, INVOICE_DEADLINE_PASSED |
500 | Something failed on our side. | BOOKING_FAILED |
502 | Stripe did not answer. Nothing was left half done, try again. | STRIPE_ERROR |
503 | An online booking owes the venue money and it has not connected Stripe. | STRIPE_NOT_CONFIGURED |
Each endpoint in the API reference lists the codes it can return. The booking endpoint checks a request in a fixed order and returns the first refusal: the body's shape, then the session exists, then the venue is live, the group size, whether the session has started, whether it is still free, the payment mode, the phone rule, the discount and voucher codes, and finally whether the room can be priced online. A body that is wrong in two ways reports the earlier one.
Pagination#
List endpoints page with limit and offset and report how much there is.
| Endpoint | Default limit | Maximum | Order | Tells you |
|---|---|---|---|---|
GET /api/embed/rooms/{slug}/availability | 200 | 500 | Soonest first | total, hasMore, and hasLater with to |
GET /api/embed/bookings | 50 | 200 | By session start, earliest first | total |
GET /api/embed/vouchers | 50 | 200 | Newest first | total |
A limit above the maximum, or a non-number, is 400 INVALID_INPUT, not clamped. Step offset by limit until you have read total rows, or until hasMore is false. The rooms list is not paged.
Rate limits#
There is no rate limiting on the Embed API today. Please still be considerate: cache the room list instead of reading it on every page view, and back off when you get a 502 or 503.
Idempotency and retries#
There are no idempotency keys, so a repeated request is a new request. Retry as follows.
- Reads (GET) are safe to repeat.
- Create booking: if the connection drops before you see the answer, do not retry blindly. The session may already be held for you. Look the booking up with
GET /api/embed/bookings?status=pendingand afromandtoaround the session. A repeat for the same session while the first hold lasts answers409 SLOT_UNAVAILABLE. - Sell voucher: a repeat creates a second voucher. Cancel an unwanted pending one with
POST /api/embed/vouchers/{code}/cancel. - Cancel is safe to repeat: cancelling a booking or voucher that is already cancelled answers
200and changes nothing. - Invoice request: a second request for the same booking or voucher is
409 INVOICE_ALREADY_REQUESTED. 502 STRIPE_ERRORon a booking or voucher means the hold was released, so it is safe to try again.
Versioning#
The API document is at version 1.0.0. There is no version in the URL or in a header, and no formal deprecation schedule yet. Write your client to ignore fields it does not know, so that a field added later does not break it.
Related#
- API keys: create, restrict and revoke keys.
- Embed booking widget: a full worked booking.
- API reference: every endpoint.
- Webhooks: verifying signed events.