Skip to content
MyEscapeRoomDocs
Esc

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

    Embed

    A venue's own booking flow (API-key auth + CORS)

    MethodPathWhat it does
    GET/api/embed/roomsThe key's venue and its active rooms
    GET/api/embed/rooms/{slug}White-label room snapshot for the booking widget
    GET/api/embed/rooms/{slug}/availabilityWhite-label availability for the widget
    GET/api/embed/bookingsThe venue's bookings by slot start
    POST/api/embed/bookingsCreate a booking through the white-label widget (shares the booking engine)
    GET/api/embed/bookings/{ref}One booking by its ref (e.g. BK-ABCD1234)
    POST/api/embed/bookings/{ref}/invoice-requestAsk for a booking's invoices after checkout
    PUT/api/embed/bookings/{ref}/invoice-requestCorrect a booking's invoice request before it is issued
    GET/api/embed/bookings/{ref}/invoicesWhere a booking's invoices stand
    POST/api/embed/bookings/{ref}/cancelCancel a booking, or release a pending hold

    The key's venue and its active rooms#

    GET/api/embed/roomsAPI key (embed)

    Each room carries allowedPaymentModes: the paymentModes a booking of it accepts, online (the room price is paid now) and/or at_location (the room price is paid at the venue; the service fee is still paid online). It is the room's own policy, or the venue's when the room has none. A booking with any other mode is refused with 422 PAYMENT_MODE_NOT_ALLOWED. pricing says how the room is priced: per-person (price × teamSize), per-game (one price), tiered (the price for exactly that group size; a size with no tier cannot be priced) or contact (not bookable through the API).

    Example request

    GET /api/embed/rooms
    curl https://myescaperoom.eu/api/embed/rooms -H "Authorization: Bearer $MER_API_KEY"

    Responses

    StatusMeaning
    200{ venue, rooms: [{ …room, allowedPaymentModes }] }
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this

    White-label room snapshot for the booking widget#

    GET/api/embed/rooms/{slug}API key (embed)

    Parameters

    NameInTypeDescription
    slugrequiredpathstring

    Responses

    StatusMeaning
    200{ room: { …room, allowedPaymentModes }, venue }
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this
    404ROOM_NOT_FOUND — no such room at the key's venue

    White-label availability for the widget#

    GET/api/embed/rooms/{slug}/availabilityAPI key (embed)

    The room's available slots (never a started, held or booked one), soonest first. Days are YYYY-MM-DD in the venue's time zone: date is one day, from (inclusive) and to (exclusive) a window. A value that is not a real day (an ISO instant, for one) is a 400 INVALID_INPUT, never ignored. The group size filters nothing: a slot is booked whole. A page is at most limit slots (200 unless asked, 500 at most); total counts every slot the filters match and hasMore says whether offset plus this page falls short of it, so page with offset until it is false. With to, hasLater says whether any slot lies on or after that day. A venue may only offer the sessions a host has said they can work for its first few days ahead (its staffing window, set by the venue): inside it, a session with no host yet is left out of every availability answer, and a booking for one is refused with 409 SLOT_UNAVAILABLE. Further ahead every free session is listed.

    Parameters

    NameInTypeDescription
    slugrequiredpathstring
    datequerystringOne venue-local day.
    fromquerystringFirst venue-local day of a window, inclusive. Plain YYYY-MM-DD only, no time or offset.
    toquerystringDay the window ends, exclusive: that day is not included. from=2026-11-02&to=2026-11-09 is the seven days 2 to 8 November. Plain YYYY-MM-DD only, no time or offset.
    limitqueryinteger
    offsetqueryinteger

    Responses

    StatusMeaning
    200{ slots, total, hasMore, hasLater? }
    400INVALID_INPUT — a day that is not YYYY-MM-DD, or a bad limit or offset
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this
    404ROOM_NOT_FOUND — no such room at the key's venue

    The venue's bookings by slot start#

    GET/api/embed/bookingsAPI key (embed)

    Parameters

    NameInTypeDescription
    fromquerystringISO 8601 with offset; slot start, from inclusive, to exclusive. A + in the offset may be sent raw or as %2B.
    toquerystringISO 8601 with offset; slot start, from inclusive, to exclusive. A + in the offset may be sent raw or as %2B.
    statusquerystringpending, confirmed or cancelled
    limitqueryintegerAt most 200
    offsetqueryinteger

    Responses

    StatusMeaning
    200{ bookings: [{ booking, room, slot }], total }. booking.guestName, booking.guestEmail, booking.phone and booking.cancellationReason are null unless the key was granted "May read guest details" on the dashboard's API page (grant it only to a key kept on your own server).
    400INVALID_INPUT
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this

    Create a booking through the white-label widget (shares the booking engine)#

    POST/api/embed/bookingsAPI key (embed)

    The same rules as POST /api/bookings, the venue's bookerPhone setting included: a venue that requires the booker's phone refuses a widget booking without one (400 PHONE_REQUIRED or PHONE_INVALID). A booking through a key is always a guest booking: userId and who-is-playing fields are not for it.

    Body: slotId, guestName (the contact the venue asks for), guestEmail, teamSize and paymentMode are required; phone, discountCode, voucherCode and invoice are optional. paymentMode is online or at_location, and the room's allowedPaymentModes (GET /api/embed/rooms) says which it takes. The booking stores no game language, notes or e-mail locale, so keep those on your side.

    Invoices. At a Belgian venue the booker can ask for invoices ("Need an invoice?"): send invoice (buyerKind business or person, name, vatNumber for an EU business, address, postalCode, city, country, email) with the booking, or later POST /api/embed/bookings/{ref}/invoice-request. Once the booking is confirmed MTEC Games VOF emails the buyer two PDFs: its own service fee invoice, and the room invoice it issues in the venue's company's name (after the game when the room is paid at the venue; none when a gift voucher paid the whole room; the venue is emailed to invoice the room itself when its invoicing profile is not complete). An EU VAT number is checked with VIES first, and a VIES that does not answer never fails the booking. A venue outside Belgium answers 422 INVOICE_NOT_AVAILABLE and nothing is booked. GET /api/embed/bookings/{ref}/invoices says where they stand.

    Hold and payment. The booking is pending and holds its slot for 15 minutes from creation; a sweep runs every minute, so an unpaid slot is back on sale within about a minute after that. It becomes confirmed when Stripe's payment_intent.succeeded reaches MyEscapeRoom, which is what the player returning from the payment page does not do: poll GET /api/embed/bookings/{ref} and read booking.status until it is confirmed or cancelled. Stripe's delivery time is not ours to promise (normally seconds; Stripe retries a failed delivery with backoff for days), so keep polling until the hold has ended before telling a player their payment failed. To give a hold back sooner, POST /api/embed/bookings/{ref}/cancel.

    Money. Everything is recomputed from the room's pricing; an amount in the body is ignored. quote.roomSubtotalCents is the room price for teamSize; discountCents and voucherCents come off it; platformFeeCents is MyEscapeRoom's flat service fee, always paid online and never discounted, on top of the room price. totalCents = room − discount − voucher + service fee, which is onlineChargeCents + dueAtVenueCents: with online the whole total is charged now, with at_location only the service fee is and the rest is dueAtVenueCents. A site's displayed total should be quote.totalCents.

    Checks run in this order and the first refusal is the answer: 1) the body's shape, all fields at once (400 INVALID_JSON, INVALID_INPUT); 2) the slot exists at the key's venue (404 SLOT_NOT_FOUND); 3) the venue is live (409 VENUE_UNAVAILABLE); 4) teamSize is within the room's minPlayers and maxPlayers (422 INVALID_TEAM_SIZE); 5) the slot has not started (409 SLOT_IN_PAST); 6) the slot is still available, and, inside the venue's staffing window, a host has said they can work it (409 SLOT_UNAVAILABLE); 7) paymentMode is one the room allows (422 PAYMENT_MODE_NOT_ALLOWED); 8) the venue's phone rule (400 PHONE_REQUIRED, PHONE_INVALID); 9) who is playing (400); 10) the discount and voucher codes (422 DISCOUNT_INVALID, VOUCHER_INVALID, VOUCHER_EMPTY); 11) the room can be priced online (422 CONTACT_PRICING). Only then is the slot held, and a code spent or a slot taken in the same instant by someone else is a 409.

    Request body

    FieldTypeDescription
    slotIdrequiredstring
    guestNamerequiredstring
    guestEmailrequiredstring
    phoneoptionalstring
    teamSizerequiredinteger
    paymentModerequiredstring
    discountCodeoptionalstring
    voucherCodeoptionalstring
    invoiceoptionalobject"Need an invoice?": the same object as POST /api/embed/bookings/{ref}/invoice-request. Belgian venues only (422 INVOICE_NOT_AVAILABLE)

    Example request

    POST /api/embed/bookings
    curl -X POST https://myescaperoom.eu/api/embed/bookings \
      -H "Authorization: Bearer $MER_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"slotId":"slot-123","guestName":"Sofie V.","guestEmail":"sofie@example.com","teamSize":4,"paymentMode":"online"}'

    Example request body

    json
    {
      "slotId": "slot-123",
      "guestName": "Sofie V.",
      "guestEmail": "sofie@example.com",
      "teamSize": 4,
      "paymentMode": "online"
    }

    Responses

    StatusMeaning
    201{ bookingId, ref, clientSecret, quote, stripePublishableKey, booking, room, slot } — booking, room and slot (its start and end) are what GET /api/embed/bookings/{ref} returns, so no second call is needed. Confirm the payment with Stripe.js: stripe = Stripe(stripePublishableKey); stripe.confirmPayment({ clientSecret, … }). The booking holds its slot for 15 minutes and is confirmed by the webhook once paid. booking.guestName, booking.guestEmail, booking.phone and booking.cancellationReason are null unless the key was granted "May read guest details" on the dashboard's API page (grant it only to a key kept on your own server).
    400INVALID_JSON; INVALID_INPUT; PHONE_REQUIRED or PHONE_INVALID — the venue's phone rule
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this
    404SLOT_NOT_FOUND — no such slot at the key's venue
    409SLOT_UNAVAILABLE — taken or held, or inside the venue's staffing window with no host available; SLOT_IN_PAST; VENUE_UNAVAILABLE; DISCOUNT_EXHAUSTED; VOUCHER_EMPTY
    422INVALID_TEAM_SIZE; PAYMENT_MODE_NOT_ALLOWED — see the room's allowedPaymentModes; CONTACT_PRICING; DISCOUNT_INVALID; VOUCHER_INVALID; INVOICE_NOT_AVAILABLE — an invoice at a venue outside Belgium
    502STRIPE_ERROR — the slot was released, retry
    503STRIPE_NOT_CONFIGURED — the venue cannot take cards yet

    One booking by its ref (e.g. BK-ABCD1234)#

    GET/api/embed/bookings/{ref}API key (embed)

    Parameters

    NameInTypeDescription
    refrequiredpathstring

    Responses

    StatusMeaning
    200{ booking, room, slot }. booking.guestName, booking.guestEmail, booking.phone and booking.cancellationReason are null unless the key was granted "May read guest details" on the dashboard's API page (grant it only to a key kept on your own server).
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this
    404BOOKING_NOT_FOUND

    Ask for a booking's invoices after checkout#

    POST/api/embed/bookings/{ref}/invoice-requestAPI key (embed)

    For a booker who asks after the booking was made. Any key may call it: the ref is the booker's own, as for cancelling. The rules are the portal's. Belgian venues only, for a pending or confirmed booking, or a paid cancellation (its service fee is kept, so only the service fee invoice is issued). Allowed until 23:59 Brussels time on the 15th of the month after the last tax point: the service fee's payment, and the room's payment or, paid at the venue, the game. One request per booking (409 INVOICE_ALREADY_REQUESTED); correct it with PUT. An EU VAT number is checked with VIES first. On a confirmed booking the invoices are issued at once and emailed to the buyer as PDFs (over Peppol as well to a Belgian business that receives them); on a pending one, once it is confirmed.

    Parameters

    NameInTypeDescription
    refrequiredpathstring

    Request body

    FieldTypeDescription
    buyerKindrequiredstring
    namerequiredstringThe person's or the company's name
    vatNumberoptionalstringRequired for a business in the EU, with its country prefix
    addressrequiredstring
    postalCoderequiredstring
    cityrequiredstring
    countryrequiredstringISO 3166-1 alpha-2, e.g. BE
    emailrequiredstringThe invoices are emailed here

    Example request body

    json
    {
      "buyerKind": "business",
      "name": "Test Buyer NV",
      "vatNumber": "BE0712345630",
      "address": "Straat 1",
      "postalCode": "1000",
      "city": "Brussel",
      "country": "BE",
      "email": "billing@testbuyer.be"
    }

    Responses

    StatusMeaning
    201The booking's invoice state, as GET /api/embed/bookings/{ref}/invoices returns it
    400INVALID_JSON; INVALID_INPUT — for example an EU business without its VAT number
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this
    404BOOKING_NOT_FOUND — no such booking at the key's venue
    409INVOICE_ALREADY_REQUESTED
    422INVOICE_NOT_AVAILABLE (a venue outside Belgium, a released hold, or nothing to invoice) or INVOICE_DEADLINE_PASSED

    Correct a booking's invoice request before it is issued#

    PUT/api/embed/bookings/{ref}/invoice-requestAPI key (embed)

    The same body and checks as POST. Once either invoice is issued, or the venue was emailed to invoice the room itself, it is 409 INVOICE_ALREADY_ISSUED: a correction then is a credit note and a reissue, which only an operator can do.

    Parameters

    NameInTypeDescription
    refrequiredpathstring

    Responses

    StatusMeaning
    200The booking's invoice state
    400INVALID_JSON; INVALID_INPUT
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this
    404BOOKING_NOT_FOUND, or INVOICE_REQUEST_NOT_FOUND (nothing to correct)
    409INVOICE_ALREADY_ISSUED
    422INVOICE_NOT_AVAILABLE or INVOICE_DEADLINE_PASSED

    Where a booking's invoices stand#

    GET/api/embed/bookings/{ref}/invoicesAPI key (embed)

    { available (a Belgian venue), requested, deadline (ISO; null while a tax point is still to come), canRequest, feeInvoice (none, pending, issued), roomInvoice (none, pending, issued, venue_issues — the venue invoices it itself, voucher_paid — a gift voucher paid the room, so there is none), roomAfterGame, invoices: [{ id, kind (service_fee or room), type, number, issuerName, issueDate, totalGrossCents, currency, status, pdfPath }] }. pdfPath is always null here: the PDFs are emailed to the buyer, never served through this API.

    Parameters

    NameInTypeDescription
    refrequiredpathstring

    Responses

    StatusMeaning
    200The booking's invoice state
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this
    404BOOKING_NOT_FOUND

    Cancel a booking, or release a pending hold#

    POST/api/embed/bookings/{ref}/cancelAPI key (embed)

    A pending booking (a customer who abandoned checkout) is released: its slot is freed at once and its PaymentIntent cancelled. Whether it is unpaid is asked of Stripe, not read from its status: a payment that went through before its webhook landed counts as paid (BOOKING_PAID; retry once the booking is confirmed). A confirmed booking is cancelled with a refund: the room money paid online goes back to the player, never the service fee; a booking paid at the venue refunds nothing.

    Who is cancelling. By default the call is a player's cancellation, held to the venue's player cancellation policy exactly as the player's own cancel in MyEscapeRoom is: allowed until the venue's notice before the slot's start (the more generous of the notice the booking was made under and the venue's current one), and refused after it with 409 CANCELLATION_DEADLINE_PASSED. A venue that lets players cancel at no time answers 403 CANCELLATION_NOT_ALLOWED. After the deadline only the venue cancels: send asVenue: true, which cancels until the slot's start as the dashboard does. That needs a key the venue has granted "May cancel after the player deadline" on its dashboard API page, and otherwise answers 403 API_KEY_NOT_PERMITTED. Grant it only to a key used from your own server: a key in a web page can be called with any body by any visitor. A key without it still cancels for players inside the policy.

    The refund itself follows within minutes, so the answer's booking.refundStatus is pending (or not_needed when nothing is owed) and booking.refundedCents the amount decided. This replaces the earlier 409 BOOKING_PAID for confirmed bookings: a caller that relied on it to refuse paid bookings now cancels them. The optional body { reason } (up to 500 characters) is recorded on the booking, with cancelledAt and cancelledBy (api), for either kind of cancellation; for a confirmed booking it is also given in the player's email, while a released pending booking emails nobody. Cancelling a booking that is already cancelled is a no-op that answers 200 with the booking, and its body is not read.

    Parameters

    NameInTypeDescription
    refrequiredpathstring

    Request body

    FieldTypeDescription
    reasonoptionalstring
    asVenueoptionalbooleanCancel as the venue, until the slot's start, past the player deadline. Needs a key granted that permission, else 403 API_KEY_NOT_PERMITTED.

    Example request body

    json
    {
      "reason": "The group asked to move to another day",
      "asVenue": false
    }

    Responses

    StatusMeaning
    200{ booking, room, slot } — cancelled, now or already; for a confirmed booking with refundStatus and refundedCents. booking.guestName, booking.guestEmail, booking.phone and booking.cancellationReason are null unless the key was granted "May read guest details" on the dashboard's API page (grant it only to a key kept on your own server).
    400INVALID_JSON; INVALID_INPUT — the reason is over 500 characters
    401UNAUTHORIZED — no key, an unknown or revoked one, or a venue that is not live. The 401 carries no CORS headers, so a browser sees a failed request rather than this body.
    403ORIGIN_NOT_ALLOWED — the request carries an Origin that is not one of the venue's allowed origins (and not the portal's own site). Nothing is done. A call with no Origin header (from a server) is never refused for this; CANCELLATION_NOT_ALLOWED — the venue lets players cancel at no time, so only the venue cancels (asVenue); API_KEY_NOT_PERMITTED — asVenue was sent with a key the venue has not granted that permission
    404BOOKING_NOT_FOUND
    409CANCELLATION_DEADLINE_PASSED — the player deadline has passed, so only the venue cancels (asVenue); GAME_STARTED — the slot has started, nothing is cancelled; BOOKING_PLAYED — a session outcome is logged; BOOKING_PAID — a pending booking whose payment went through, retry once it is confirmed; PAYMENT_PROCESSING — Stripe is still processing a pending booking's payment, try again later
    502STRIPE_ERROR — Stripe could not be asked whether a pending booking is paid; nothing was released, retry
    MyEscapeRoom Docs