Skip to content
MyEscapeRoomDocs
Esc

    Type to search. Use the arrow keys and Enter to open a result.

    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:

    text
    https://myescaperoom.eu
    • Requests and responses are JSON. Send Content-Type: application/json on a request with a body.
    • Money is in integer cents (amountCents: 10000 is 10,000 cents, 100.00 euros), with a currency where it matters.
    • Instants (start, createdAt) are ISO 8601 strings in UTC, such as 2026-11-02T17:00:00.000Z. Show them in the venue's time zone, which the venue object gives as timezone.
    • A day (date, from, to on availability) is a plain YYYY-MM-DD in the venue's time zone.

    Three ways to call the API#

    HowUsed byProves
    None (public)Discovery, public room pages, the quote endpoint, leaderboardsNothing. Anyone may call it.
    Venue API keyEvery /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 cookieThe 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 are null in every booking answer, and the buyer of a voucher is null in 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.

    What the browser sees
    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, POST and OPTIONS are allowed from a browser, and only the headers Content-Type, Authorization and x-api-key. PUT /api/embed/bookings/{ref}/invoice-request therefore 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 401 or an ORIGIN_NOT_ALLOWED lacks the Access-Control-Allow-Origin header, 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.

    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:

    json
    { "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:

    400 INVALID_INPUT
    {
      "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.

    Handling a failure
    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#

    StatusMeaningCodes you may see
    400The request itself was refused: not JSON, or a field is missing or out of range.INVALID_JSON, INVALID_INPUT, PHONE_REQUIRED, PHONE_INVALID
    401No key, an unknown or revoked key, or a venue that is not live.UNAUTHORIZED
    403The 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
    404Nothing there, or it belongs to another venue.SLOT_NOT_FOUND, ROOM_NOT_FOUND, BOOKING_NOT_FOUND, VOUCHER_NOT_FOUND, INVOICE_REQUEST_NOT_FOUND
    409The 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
    422Well 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
    500Something failed on our side.BOOKING_FAILED
    502Stripe did not answer. Nothing was left half done, try again.STRIPE_ERROR
    503An 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.

    EndpointDefault limitMaximumOrderTells you
    GET /api/embed/rooms/{slug}/availability200500Soonest firsttotal, hasMore, and hasLater with to
    GET /api/embed/bookings50200By session start, earliest firsttotal
    GET /api/embed/vouchers50200Newest firsttotal

    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=pending and a from and to around the session. A repeat for the same session while the first hold lasts answers 409 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 200 and changes nothing.
    • Invoice request: a second request for the same booking or voucher is 409 INVOICE_ALREADY_REQUESTED.
    • 502 STRIPE_ERROR on 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.

    MyEscapeRoom Docs