Skip to content
innovorder
⌘K

Integration path

White-label Guest App

The Innovorder Guest App serves employees and other diners in collective catering. Each enterprise deployment has its own Innovorder brand and published hostname. A catering operator can serve several enterprise deployments; a diner can only access their own brand and the restaurants configured for that deployment.

This page documents the built-in Guest App API. The third-party integration guide covers a separate service-account integration pattern. All identifiers and example hostnames below are fictional. Provider credentials and deployment activation must be provisioned before the corresponding features are available.

Guest App partner embeds

A brand can have several applications, each with its own UUID, domain, publication and integration keys. Complete the API migration and BFF rollout before publishing a second application. Normal and embedded BFF requests both carry the selected deployment UUID; older direct menu/rating calls without it are accepted only when exactly one application is published for the brand.

Badge grants expose the badge and Wallet only; balance access requires the payments or full-app grant. Payment bootstrap includes account payment type for eligibility, never school/class, pricing identifiers or a badge barcode. Saved cards remain manageable when automatic reload is available while manual top-up is disabled; this does not enable manual charges.

Embed an existing published application or one of its modules. An Innovorder administrator creates an integration with exact HTTPS parent origins, permitted modules and a dedicated HS256 key. The secret is displayed once, belongs on the partner server only, and must never be included in HTML, JavaScript, a mobile bundle or a URL.

The deployment identifier is a stable UUIDv4. Both addresses below work simultaneously; the shared address does not redirect to the customer domain. Shared access requires publication, but does not require customer DNS activation. A customer hostname must be active and belong to this exact deployment. Drafts are never exposed.

text
https://guest-embed.innovorder.io/embed/11111111-1111-4111-8111-111111111111/menus
https://cantine.customer.example/embed/11111111-1111-4111-8111-111111111111/menus

Dev: guest-embed-dev.innovorder.io
Preproduction: guest-embed-preprod.innovorder.io
Path suffixJWT moduleContent
/transactionstransactionsSales and wallet history, details and PDF receipts
/topup, /cards, /auto-reloadpaymentsStripe top-up, saved cards and automatic reload, subject to account eligibility
/badgebadgeBadge and Apple/Google Wallet
/menusmenusWeekly menus, restaurants, dish details and private ratings
/app/*appFull application and its internal navigation

Supported URL parameters: integrationId (required), locale, restaurantId, headerless=true and footerless=true. The restaurant must belong to the published deployment. Standalone modules hide surrounding navigation by default; full app preserves it. Headerless also hides the desktop sidebar; footerless also hides mobile navigation. Payment controls and links between financial screens remain available.

Server-generated token

Use a new JWT for every connection and renewal. HS256 header: kid from the integration. Claims below are mandatory, with exactly one existing customer identity: numeric customerId, string badgeNumber (preserve leading zeros), or email. Resolve that identity from the authenticated partner user on your server. Never sign a customer ID, module or origin supplied by an unauthenticated browser.

json
{
  "iss": "urn:innovorder:guest-embed:integration:22222222-2222-4222-8222-222222222222",
  "aud": "urn:innovorder:guest-embed:11111111-1111-4111-8111-111111111111",
  "iat": 1791194400,
  "exp": 1791194460,
  "jti": "44444444-4444-4444-8444-444444444444",
  "module": "menus",
  "parentOrigin": "https://portal.customer.example",
  "targetOrigin": "https://guest-embed.innovorder.io",
  "badgeNumber": "0000123456"
}

Maximum validity is 60 seconds. Generate jti with crypto.randomUUID(); it is single-use across both Guest addresses. The target origin must match the chosen URL. Legacy Ordering JWTs are rejected. Claims cannot create accounts or change profiles, balances or permissions. Ambiguous identities are rejected.

javascript
// Partner server: run after your user authentication and CSRF middleware.
// Derive customerId from your trusted user-to-Innovorder mapping.
import jwt from 'jsonwebtoken';
import { randomUUID } from 'node:crypto';
const now = Math.floor(Date.now() / 1000);
const autoLoginJWT = jwt.sign({
  customerId: authenticatedInnovorderCustomerId,
  module: 'menus',
  parentOrigin: 'https://portal.customer.example',
  targetOrigin: 'https://guest-embed.innovorder.io',
  iat: now, exp: now + 60, jti: randomUUID()
}, serverSecrets.guestEmbedKey, {
  algorithm: 'HS256', keyid: '33333333-3333-4333-8333-333333333333',
  issuer: 'urn:innovorder:guest-embed:integration:22222222-2222-4222-8222-222222222222',
  audience: 'urn:innovorder:guest-embed:11111111-1111-4111-8111-111111111111'
});
// Return { autoLoginJWT } with Cache-Control: no-store to that user only.

Browser SDK v1

html
<script src="https://guest-embed.innovorder.io/embed-sdk/v1.js"></script>
<div id="guest"></div>
<script>
const guest = GuestAppEmbed.mount(document.getElementById('guest'), {
  url: 'https://guest-embed.innovorder.io/embed/11111111-1111-4111-8111-111111111111/menus',
  integrationId: '22222222-2222-4222-8222-222222222222',
  locale: 'fr',
  getToken: async (context) => {
    const response = await fetch('/api/guest-embed-token', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(context)
    });
    if (!response.ok) throw new Error('Authentication unavailable');
    return (await response.json()).autoLoginJWT;
  },
  onEvent: (event) => { /* Update host loading/error UI. No credentials or balance amounts. */ }
});
// guest.setLocale('en'); guest.setRestaurant(200); guest.navigate('/menus');
// guest.logout(); // Stops automatic authentication; mount again for a new login.
// guest.destroy();
</script>

The SDK pins the chosen Guest origin, iframe window and message channel. It obtains tokens through your authenticated server callback and passes them by message. It automatically renews the 15-minute session before expiry and reconnects after iframe reload. The iframe stores only an opaque session in memory; OAuth stays server-side in Redis. Neither cookies nor persistent browser storage are needed. Multiple iframes have independent sessions.

Events: ready, authenticated, expired, logged-out, error, navigation (local path) and balance-changed (no amount) and resize (height in pixels). Module height adjusts automatically; the full app fills its container with default height 100dvh. Set the SDK height option to a CSS height when necessary. Calls to navigate outside the authorized module are rejected.

For financial modules, prefer automatic height so the host page owns scrolling. On a screen change, the SDK brings a partially scrolled, visible embed back to its start. It does not scroll on initial load, refresh, or when the embed is outside the viewport. If your portal has a fixed header, set --guest-embed-scroll-offset: 64px on the container (using your actual header height). Reserve space for any fixed host footer in the host layout; Guest cannot measure elements outside its iframe.

Direct iframe URLs also support autoLoginJWT, removed immediately by the renderer. This requires an allowed parent referrer; prefer the SDK when referrers are suppressed and for renewal/reload. Do not log these URLs in partner proxies or analytics. Guest document/token routes and API responses are not cached.

Payments, downloads and nested applications

The SDK grants iframe permissions allow="payment" and sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads". Preserve these permissions when mounting manually. Allow the chosen Guest origin in your own frame-src policy. Payment and Wallet external openings require a user action; Guest issues a short single-use continuation bound to the original deployment and origin. Reconnection never creates a new charge. Keep the original tab open for payment return.

Full-app Ordering and Atlas receive a validated embed context. All iframe ancestors must be allowed, including the partner portal and the chosen Guest address. Atlas origins are scoped to the current integration. For custom partner modules, configure those origins in the external application's frame-ancestors policy; a parent cannot override a third party's CSP. Generated design components retain their opaque sandbox and receive no customer identity or session token.

Visual previews

Innovorder administrators can open Preview embeds in the application's Integrations tab. All seven views use the current draft's appearance and modules, with mobile/desktop, language and header/footer controls. A fictional guest is already signed in; restarting the session resets the preview. This isolated preview works before publication and DNS activation. It does not generate or validate a real JWT, check a partner origin, charge a card or modify customer data. Validate those operations separately with the authenticated partner integration.

Administration and lifecycle

Innovorder admins manage integrations under Configuration → Integrations / Embeds. Updates, disabling and key rotation invalidate active sessions through a revision check. Configuration history never contains keys and restoring an editorial version cannot restore a revoked integration. Domain changes preserve the deployment UUID and shared URL.

Browser traffic uses /api/embed/:deploymentId. The internal API exchange under /guest-app/embed/bff requires the environment's Guest Cloud Run service identity and a dedicated GCP audience. It is not a public OAuth login endpoint. Never call it from a partner browser or distribute that identity.

Restaurant reservations

Enable features.reservations and add reservations to the published module order. Historical applications keep this optional module disabled. The account history remains readable after disabling new bookings. Preview uses fictitious data and cannot create a reservation.

Reservations allocate places in a dated restaurant slot; they do not assign physical tables. Pending and confirmed bookings consume capacity. Availability is rechecked under a database lock at creation; never treat the displayed count as a guarantee. A customer can have one active booking per slot. Settings and capacity belong to the restaurant, shared across its Guest applications; each booking retains its original application and customer.

Rules and staff access

These administration routes allow Innovorder admins and users of the owning brand. Restaurant and brand-group roles are excluded. The application, restaurant and stored brand are checked server-side. Settings/slots require membership of the application; historical bookings remain manageable after restaurant removal. Revisions are required on mutations; a stale revision returns 409 guest_reservation_conflict.

GET/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}/settingsRead reservation rules

Returns defaults with revision 0 until saved: manual approval, 30-day horizon, online cancellation until 120 minutes before start, contact notifications off. Timezone comes from the application restaurant configuration.

PUT/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}/settingsSave reservation rules

advanceDays: 0–365 calendar days, including today; 0 means same-day only. cancellationCutoffMinutes: 0–43200. Contact email is required when notifyContact is true. Existing bookings retain their original cancellation deadline. Does not publish the Guest feature or alter restaurant timezone.

Request Body

json
{
  "revision": 0,
  "approvalMode": "manual",
  "advanceDays": 30,
  "guestCancellationEnabled": true,
  "cancellationCutoffMinutes": 120,
  "contactEmail": "restaurant@example.org",
  "notifyContact": false,
  "location": "10 Example Street, Paris"
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
revisioninteger0The revision value.
approvalModestring"manual"The approval mode value.
advanceDaysinteger30The advance days value.
guestCancellationEnabledbooleantrueThe guest cancellation enabled value.
cancellationCutoffMinutesinteger120The cancellation cutoff minutes value.
contactEmailstring"restaurant@example.org"The contact email value.
notifyContactbooleanfalseThe notify contact value.
locationstring"10 Example Street, Paris"The location value.

GET/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}/slotsList dated slots

Parameters

NameTypeRequiredDescription
fromYYYY-MM-DDYesFirst local service date.
toYYYY-MM-DDYesLast local date, included. Maximum period: 93 days; up to 500 slots.

POST/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}/slotsCreate a dated reservation slot

Local dates use the restaurant IANA timezone. Nonexistent or ambiguous daylight-saving times are rejected. Start must be in the future (within 730 days), end after start, duration at most 7 days. Capacity/maxPartySize are positive integers, maxPartySize cannot exceed capacity. Enabled or booked slots cannot overlap.

Request Body

json
{
  "revision": 0,
  "startsLocal": "2026-11-12T12:00",
  "endsLocal": "2026-11-12T13:30",
  "capacity": 30,
  "maxPartySize": 6,
  "enabled": true
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
revisioninteger0The revision value.
startsLocalstring"2026-11-12T12:00"The starts local value.
endsLocalstring"2026-11-12T13:30"The ends local value.
capacityinteger30The capacity value.
maxPartySizeinteger6The max party size value.
enabledbooleantrueThe enabled value.

PUT/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}/slots/{slotId}Edit or enable/disable a slot

Disabling blocks new bookings without cancelling existing ones. Capacity cannot fall below reserved places. A future slot with active bookings cannot be moved.

Request Body

json
{
  "revision": 1,
  "startsLocal": "2026-11-12T12:00",
  "endsLocal": "2026-11-12T13:30",
  "capacity": 30,
  "maxPartySize": 6,
  "enabled": false
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
revisioninteger1The revision value.
startsLocalstring"2026-11-12T12:00"The starts local value.
endsLocalstring"2026-11-12T13:30"The ends local value.
capacityinteger30The capacity value.
maxPartySizeinteger6The max party size value.
enabledbooleanfalseThe enabled value.

DELETE/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}/slots/{slotId}Delete a slot while retaining booking history

Soft deletion. Rejects future slots with pending or confirmed bookings; cancel those bookings first.

Request Body

json
{
  "revision": 2
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
revisioninteger2The revision value.

GET/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}List bookings and restaurant KPIs

Required from/to local dates (maximum 93 days), offset, limit (up to 200), view=all|pending|upcoming|past. Returns items, total, serverNow and KPIs over the full date period, independent of the selected view/page: total, pending, confirmed, confirmedGuests, cancelled, attended, noShows and noShowRate. The rate denominator is attended + noShows; null before any attendance is recorded.

POST/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}/{reservationId}/actionsConfirm, cancel or record attendance

Actions: confirm, cancel, attended, no_show, attendance_unknown. Cancellation reasons (optional): restaurant_closed, capacity_unavailable, private_event, service_disruption, guest_request. Pending cancellation becomes DECLINED; confirmed cancellation becomes CANCELLED. Optional comment up to 1000 characters is sent to the diner. Attendance can be marked after start; no-show only after end. Attendance is correctable and does not trigger email. Cancellation is unavailable after end or while attendance is recorded.

Request Body

json
{
  "revision": 1,
  "action": "cancel",
  "reason": "restaurant_closed",
  "comment": "The restaurant will be closed that day."
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
revisioninteger1The revision value.
actionstring"cancel"The action value.
reasonstring"restaurant_closed"The reason value.
commentstring"The restaurant will be closed that day."The comment value.

Customer booking

GET/guest-app/reservations/deployments/{deploymentId}/restaurants/{restaurantId}/slotsRead available reservation slots

Authenticated customer only, enabled module and authorized published restaurant. Optional date=YYYY-MM-DD defaults to the restaurant’s current local date. Returns serverNow, minDate, maxDate, public settings, slots and remainingSeats. Started or disabled slots are excluded. Optional month=current or YYYY-MM adds calendar.month and calendar.days, each with date and maxPartySize (maximum party that fits an available slot). Dates absent from this complete monthly list are unavailable; filter maxPartySize for the selected party. The month must intersect the booking window. With month and no date, daily slots use the first date of the month inside the booking window. Calendar reads use the restaurant timezone, account for pending/confirmed reservations and paginate internally beyond 500 slots. Creation still rechecks capacity under a lock. Deploy API and BFF before enabling the monthly calendar renderer.

POST/guest-app/reservationsRequest a reservation

Customer bearer required. Identity/email come from the existing account; firstName and lastName are accepted only as fallback when the account lacks them. No customerId, status, balance or email claims. A valid account email and phone are required. UUIDv4 attemptId is stable across network retries; changed contents with the same identifier return 409. Returns 201 and PENDING (manual approval) or CONFIRMED (automatic). 60 attempts/minute per customer at API, 20 at BFF; 429 has standard retry behavior. No payment is created.

Request Body

json
{
  "deploymentId": "11111111-1111-4111-8111-111111111111",
  "slotId": "22222222-2222-4222-8222-222222222222",
  "attemptId": "33333333-3333-4333-8333-333333333333",
  "partySize": 2,
  "phone": "+33123456789",
  "comment": "A high chair if possible.",
  "locale": "en"
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
slotIdstring"22222222-2222-4222-8222-222222222222"Identifier of the associated slot.
attemptIdstring"33333333-3333-4333-8333-333333333333"Identifier of the associated attempt.
partySizeinteger2The party size value.
phonestring"+33123456789"Phone number.
commentstring"A high chair if possible."The comment value.
localestring"en"The locale value.

GET/guest-app/reservations/deployments/{deploymentId}List my reservations

view=all|upcoming|past, offset, limit. Only the authenticated customer in this deployment and brand. Ended unapproved requests are represented as EXPIRED. Attendance is separate from booking status.

GET/guest-app/reservations/deployments/{deploymentId}/attempts/{attemptId}Reconcile an uncertain booking request

Returns reservation or null. A lost response never warrants a new attemptId or an automatic second booking. Reuse the original idempotency identifier for retry.

GET/guest-app/reservations/deployments/{deploymentId}/{reservationId}Read my booking and cancellation eligibility

Includes canCancel and cancellationDeadlineAt, evaluated with server time. Private link material is never included.

POST/guest-app/reservations/deployments/{deploymentId}/{reservationId}/cancelCancel my booking

Requires explicit confirmation; no GET mutation. Cancellation is allowed strictly before the frozen deadline. Closed windows return 409 guest_reservation_cancellation_closed. Repeated cancellation of an already cancelled booking is harmless and does not enqueue another email.

Request Body

json
{
  "confirm": true
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
confirmbooleantrueThe confirm value.

Emails and private management links

The transactional outbox queues guest and optional restaurant-contact notifications with booking changes. The minute job retries publication to the existing email service. Delivery is at-least-once; an SMTP delivery is not proven merely because a Pub/Sub message was accepted. Latest status supersedes older queued messages. Email delivery failures do not roll back a successfully committed reservation.

Emails explicitly distinguish pending from confirmed and include a UTF-8 calendar attachment with the slot timezone converted to UTC, location, stable UID and sequence. Pending events are TENTATIVE. Guest messages include private view/cancel links, valid until 90 days after the slot ends. The capability is in the URL fragment, removed immediately by the browser and held in memory; reloading requires reopening the email link. It grants only viewing/cancelling that booking and never logs the diner in.

POST/guest-app/reservations/public/{deploymentId}/{reservationId}/viewRead a booking using its private email link

No OAuth exchange. The signed opaque capability binds brand, deployment, booking, nonce and expiry. Invalid/expired link: 401. Guest browser uses /api/reservation-access/:deploymentId/:reservationId/view on the shared embed domain or the matching ready customer domain, with exact-origin validation and no cookies. Link previews and GET requests never cancel a booking.

Request Body

json
{
  "reservationToken": "<private-capability>"
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
reservationTokenstring"<private-capability>"The reservation token value.

POST/guest-app/reservations/public/{deploymentId}/{reservationId}/cancelCancel using its private email link

No OAuth exchange. The signed opaque capability binds brand, deployment, booking, nonce and expiry. Invalid/expired link: 401. Guest browser uses /api/reservation-access/:deploymentId/:reservationId/cancel on the shared embed domain or the matching ready customer domain, with exact-origin validation and no cookies. Link previews and GET requests never cancel a booking.

Request Body

json
{
  "reservationToken": "<private-capability>",
  "confirm": true
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
reservationTokenstring"<private-capability>"The reservation token value.
confirmbooleantrueThe confirm value.

Authenticated browser routes live under /api/reservations; the BFF supplies the deployment UUID from its session. Full-app embeds support reservations; narrower transaction, menus, badge and payment embeds do not acquire this permission. Every response is no-store and stays outside the PWA cache.

Before enabling the module: deploy the email privacy filters, apply migration 20261006010000-create-guest-reservations, deploy API and the dedicated guest_reservation_notifications job, then Guest/backoffice. The job uses the existing email topic and a topic-scoped publisher identity; no per-client secret is required. Do not enable client reservations until email processing and the shared Guest domain are operational.

Authentication and browser boundary

The Guest App browser calls its own /api/* backend. That backend authenticates an Innovorder customer, stores the OAuth bearer token in Redis and exposes only a Secure HttpOnly session cookie. Every mutation requires the exact parent origin and an x-csrf-token from GET /api/bootstrap. The Innovorder endpoints serving diners use Authorization: Bearer <customer-token>. Guest App editing requires an administrator or brand backoffice bearer token, with server-side brand ownership checks. Restaurant and brand-group roles are excluded. Application creation and domain/HTTPS operations remain administrator-only. Configuration reads remain public. Their JSON responses use the standard {status, code, message, data} envelope.

Signup, required fields, CAPTCHA, editable email, Stripe recharge and automatic recharge are derived from the published deployment and the live Innovorder brand configuration. The app does not enable unsupported features by hiding a button alone. Supported locales are fr, en, es, de, it, ar, zh-CN, ja, ko and nl; Arabic uses RTL. Dish translations are entered manually, with a fallback to the dish source language when a translation is missing.

Configuration uses a Modules editor for visibility and home ordering. Communications are enabled when the historical configuration omits features.communications; false returns an empty feed without deleting published content. Custom modules are administrator-configured HTTPS destinations, presented in the app as sandboxed iframes. They use a backend-verified SSO proof, never an Innovorder OAuth token. Preview does not load external modules.

POST/guest-app/custom-module-sessionCreate a short-lived custom module SSO proof

Authenticated convive only. Requires a published, enabled module and an authorized restaurant in the session deployment. Returns no-store. HS256 JWT expires in 60 seconds; issuer innovorder-guest-app, audience urn:innovorder:guest:<deploymentId>:<moduleId>. Claims include sub, firstName, lastName, email, deploymentId, moduleId, brandId, restaurantId, parentOrigin, targetOrigin, iat, exp and jti. The partner backend must validate all expected claims and atomically reject replayed jti values. The Guest BFF delivers the token through a strict-origin iframe postMessage handshake, never a URL parameter. The external app must implement guest.custom.ready/session/authenticated/error/close messages with version 1 and verify the parent window and origin.

Request Body

json
{
  "deploymentId": "11111111-1111-4111-8111-111111111111",
  "moduleId": "89bb7181-96b2-45b8-b89a-464414bb3518",
  "restaurantId": 200
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
moduleIdstring"89bb7181-96b2-45b8-b89a-464414bb3518"Identifier of the associated module.
restaurantIdinteger200Identifier of the restaurant.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "url": "https://partner.example.org/app",
    "token": "<short-lived-signed-proof>",
    "expiresAt": "2026-09-24T12:01:00Z"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.urlstring"https://partner.example.org/app"The url value.
data.tokenstring"<short-lived-signed-proof>"The token value.
data.expiresAtstring"2026-09-24T12:01:00Z"Date or timestamp for expires.

GET/guest-app/deployments/{deploymentId}/history/{entryId}/receipts/{receiptId}Download a sales receipt or eligible e-wallet reload PDF

Authenticated convive only. Use a receipt key returned by the history detail receipts array. Ownership, deployment restaurants, association with the history entry and reload eligibility are checked again at download. Sales use the exact report-order snapshot; reloads reuse the shared paid, non-cancelled eligibility. Binary application/pdf with Content-Disposition attachment and Cache-Control no-store. Shared ticket-export quota: 60 requests/minute per actor (429 with Retry-After). No OAuth token is passed in a URL.

Parameters

NameTypeRequiredDescription
deploymentIdUUIDYesAuthorized Guest application.
entryIdstringYesHistory key, sale:<id> or wallet:<id>.
receiptIdstringYesReceipt key from the selected history detail.

Response

json
Binary PDF. Missing, unrelated or ineligible documents are unavailable; no fallback to another customer or restaurant.

GET/guest-app/configurationResolve a published guest deployment

Public read by exact normalized hostname. Draft configuration and private provider profiles are never returned. An unknown or unpublished hostname is unavailable.

Parameters

NameTypeRequiredDescription
hostnamestringYesPublished hostname, without scheme or path.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "deploymentId": "11111111-1111-4111-8111-111111111111",
    "brandId": 100,
    "hostname": "guests.example.test",
    "name": "Example enterprise",
    "version": 1,
    "topUpLimits": {
      "minimumAmount": 100,
      "maximumAmount": 40000
    },
    "configuration": {
      "displayName": "Example restaurant",
      "defaultLocale": "fr",
      "locales": [
        "fr",
        "en"
      ],
      "country": "FR",
      "currency": "EUR",
      "timeZone": "Europe/Paris",
      "restaurants": [
        {
          "restaurantId": 200,
          "name": "Main restaurant"
        }
      ]
    }
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
data.brandIdinteger100Identifier of the brand.
data.hostnamestring"guests.example.test"The hostname value.
data.namestring"Example enterprise"The name value.
data.versioninteger1The version value.
data.topUpLimitsobject{…}Object containing top up limits fields.
data.topUpLimits.minimumAmountinteger100The minimum amount value.
data.topUpLimits.maximumAmountinteger40000The maximum amount value.
data.configurationobject{…}Object containing configuration fields.
data.configuration.displayNamestring"Example restaurant"The display name value.
data.configuration.defaultLocalestring"fr"The default locale value.
data.configuration.localesarray[…]List of locales entries.
data.configuration.locales[]string"fr"The locales value.
data.configuration.countrystring"FR"The country value.
data.configuration.currencystring"EUR"ISO 4217 currency code.
data.configuration.timeZonestring"Europe/Paris"The time zone value.
data.configuration.restaurantsarray[…]List of restaurants entries.
data.configuration.restaurants[]object{…}Object containing restaurants fields.
data.configuration.restaurants[].restaurantIdinteger200Identifier of the restaurant.
data.configuration.restaurants[].namestring"Main restaurant"The name value.

The configuration example is shortened. Its full contract includes brandHash; logo, icon and hero URLs; primary, accent and background colors; constrained typography, corner radius and card style; module ordering; badge format; feature flags; restaurant ordering/Atlas URLs; and legal/support links. Top-level topUpLimits are read-only values supplied by the API, not theme configuration or browser-selected limits. Administrators and owning brand users can publish and restore deployment versions. Brand writes and restorations preserve technical settings: brand binding, domain, country, currency, time zones, badge format, integration identifiers and custom URLs. Brand users can edit identity, languages, assistance, restaurants and administrator-enabled services; automatic integration links remain server-generated. Publication validates that configured restaurants and currency belong to the dedicated brand.

Restaurant audiences

Each configured restaurant can optionally restrict access with audience: enabled, guestGroupIds and legacyClasses. Omitted or disabled rules allow all application diners. An enabled rule requires at least one selected group. Group IDs must belong to the deployment brand; selecting a parent includes its descendants. Legacy classes match exactly and apply only when the stored student has no structured guestGroupId. Missing, deleted, foreign or ambiguous memberships grant no restricted access. Rules are saved and published with the deployment configuration.

The editor setup returns restaurantAudiences (guestGroups with parentGuestGroupId, and legacyClasses). Published anonymous configuration omits restricted restaurants. Authenticated lists never expose audience rules. Membership is read from the primary database, not from browser data or partner JWT claims. Menus, ratings, Ordering SSO including ticket consumption, Atlas, custom module sessions and new reservations recheck access. Existing personal reservations remain readable and cancellable under their original ownership rules. Atlas provisioning uses separate authenticated server callbacks so its technical restaurant scope does not expose filtered restaurants publicly.

GET/guest-app/deployments/{deploymentId}/restaurantsList restaurants accessible to the authenticated diner

Customer OAuth authentication required. Resolves the exact published deployment and brand. Returns an empty list when no restaurant is accessible. Cache-Control: no-store. The Guest BFF relays this through GET /api/restaurants or /api/embed/{deploymentId}/restaurants; embeds retain their module permissions. The renderer refreshes at login, on focus and each visible minute. Existing Ordering channels outside Guest App retain their own access policies.

Parameters

NameTypeRequiredDescription
deploymentIdstring (UUIDv4)YesPublished Guest App deployment.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": [
    {
      "restaurantId": 200,
      "name": "Main restaurant",
      "orderingUrl": "https://ordering.example.test/brand"
    }
  ]
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataarray[…]Endpoint-specific response payload.
data[]object{…}Endpoint-specific response payload.
data[].restaurantIdinteger200Identifier of the restaurant.
data[].namestring"Main restaurant"The name value.
data[].orderingUrlstring"https://ordering.example.test/brand"The ordering url value.

Automatic deployment setup

GET/guest-app/admin/deployments/{deploymentId}/setupPrepare the administration editor

Administrators and owning brand users. Returns permissions (technical, createApplication, editableFeatures), brand defaults, the available restaurants, resolved v1/v2 entry links and configuration availability for Ordering and Atlas. No signing key, partner secret or customer token is returned.

Parameters

NameTypeRequiredDescription
deploymentIdstring (UUID)YesDeployment being configured.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "brand": {
      "brandId": 100,
      "name": "Example company",
      "brandHash": "example",
      "currency": "EUR",
      "locale": "fr"
    },
    "ordering": {
      "configured": true
    },
    "atlas": {
      "automatic": true,
      "configured": false,
      "status": "pending",
      "embedUrl": "https://atlas.example/embed/guest-footfall"
    },
    "restaurants": [
      {
        "restaurantId": 200,
        "name": "Main restaurant",
        "orderingUrls": {
          "v1": "https://ordering-v1.example/home?brandId=100",
          "v2": "https://ordering-v2.example/example"
        }
      }
    ]
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.brandobject{…}Object containing brand fields.
data.brand.brandIdinteger100Identifier of the brand.
data.brand.namestring"Example company"The name value.
data.brand.brandHashstring"example"The brand hash value.
data.brand.currencystring"EUR"ISO 4217 currency code.
data.brand.localestring"fr"The locale value.
data.orderingobject{…}Object containing ordering fields.
data.ordering.configuredbooleantrueThe configured value.
data.atlasobject{…}Object containing atlas fields.
data.atlas.automaticbooleantrueThe automatic value.
data.atlas.configuredbooleanfalseThe configured value.
data.atlas.statusstring"pending"HTTP status code returned by the API.
data.atlas.embedUrlstring"https://atlas.example/embed/guest-footfall"The embed url value.
data.restaurantsarray[…]List of restaurants entries.
data.restaurants[]object{…}Object containing restaurants fields.
data.restaurants[].restaurantIdinteger200Identifier of the restaurant.
data.restaurants[].namestring"Main restaurant"The name value.
data.restaurants[].orderingUrlsobject{…}Object containing ordering urls fields.
data.restaurants[].orderingUrls.v1string"https://ordering-v1.example/home?brandId=100"The v1 value.
data.restaurants[].orderingUrls.v2string"https://ordering-v2.example/example"The v2 value.

Automatic customer domains

Creating an application also initializes durable domain onboarding. An Innovorder administrator copies an A record and the exact Certificate Manager CNAME to the customer's DNS provider. Verification continues without an open browser. A domain becomes ready only after DNS, certificate, certificate-map association and a TLS health check succeed. Content must also be explicitly published; DNS verification never publishes a draft.

GET/guest-app/admin/deployments/{deploymentId}/domainRead domain onboarding and DNS instructions

Admin only. Returns preparation state, DNS and certificate status, exact DNS values, last/next checks, safe errors, publication status and whether a pending hostname can be corrected. No private keys or Google resource references are returned.

Parameters

NameTypeRequiredDescription
deploymentIdUUIDYesDeployment identifier.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "hostname": "lunch.customer.fr",
    "revision": 1,
    "state": "dns_pending",
    "ready": false,
    "legacy": false,
    "editable": true,
    "published": false,
    "dnsState": "missing",
    "certificateState": "PROVISIONING",
    "checkedAt": null,
    "nextCheckAt": "2026-09-18T12:00:00Z",
    "error": "dns_missing",
    "records": [
      {
        "type": "A",
        "name": "lunch.customer.fr",
        "relativeName": "lunch",
        "value": "34.1.2.3",
        "purpose": "routing"
      },
      {
        "type": "CNAME",
        "name": "_acme-challenge_example.lunch.customer.fr.",
        "relativeName": "_acme-challenge_example.lunch",
        "value": "example.authorize.certificatemanager.goog.",
        "purpose": "certificate"
      }
    ]
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.hostnamestring"lunch.customer.fr"The hostname value.
data.revisioninteger1The revision value.
data.statestring"dns_pending"The state value.
data.readybooleanfalseThe ready value.
data.legacybooleanfalseThe legacy value.
data.editablebooleantrueThe editable value.
data.publishedbooleanfalseThe published value.
data.dnsStatestring"missing"The dns state value.
data.certificateStatestring"PROVISIONING"The certificate state value.
data.checkedAtnullnullDate or timestamp for checked.
data.nextCheckAtstring"2026-09-18T12:00:00Z"Date or timestamp for next check.
data.errorstring"dns_missing"The error value.
data.recordsarray[…]List of records entries.
data.records[]object{…}Object containing records fields.
data.records[].typestring"A"The type value.
data.records[].namestring"lunch.customer.fr"The name value.
data.records[].relativeNamestring"lunch"The relative name value.
data.records[].valuestring"34.1.2.3"The value value.
data.records[].purposestring"routing"The purpose value.

POST/guest-app/admin/deployments/{deploymentId}/domain/checkPrioritize a domain check

Admin only. Idempotently makes the existing background request due. It does not launch another concurrent worker, wait for DNS propagation or publish content.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "scheduled": true
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.scheduledbooleantrueThe scheduled value.

PUT/guest-app/admin/deployments/{deploymentId}/domainChange a pending or active domain

Admin only. Requires the domain revision from GET /domain. Rejects duplicates, stale revisions and checks currently holding a lease. For an active or legacy domain, keeps the serving hostname until its replacement passes DNS, certificate, map and HTTPS checks. Pending corrections retire only the superseded onboarding. Cutover atomically updates the deployment hostname and revision and retires the previous domain; its certificate cleanup is deferred by one day. Saved and published content remain unchanged. GET /domain returns the target hostname plus deploymentId, deploymentHostname and deploymentRevision for the currently serving deployment. Use the returned deployment revision for saves; guests must sign in on the new domain. The old address is no longer served after cutover.

Request Body

json
{
  "hostname": "correct.customer.fr",
  "revision": 1
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
hostnamestring"correct.customer.fr"The hostname value.
revisioninteger1The revision value.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "deploymentId": "11111111-1111-4111-8111-111111111111",
    "hostname": "correct.customer.fr",
    "revision": 2
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
data.hostnamestring"correct.customer.fr"The hostname value.
data.revisioninteger2The revision value.

States: preparing, dns_pending, certificate_pending, activating, ready; retired applies to replaced domains. Errors distinguish dns_missing, dns_wrong_target, dns_ipv6_conflict, dns_unavailable, certificate_blocked, google_unavailable, https_pending and platform_unavailable. Existing domains retain their current certificate during migration. The public configuration adds domainReady; the Guest BFF does not open new domains while it is false. An application is reachable only when technically activated and published. Domains are checked every minute initially, every fifteen minutes after 24 hours, and hourly after activation. Keep both DNS records for renewal.

POST/guest-app/admin/deployments/:deploymentId/atlas-syncSynchronize the managed Atlas embed partner

Admin only. Idempotent synchronization from the current published configuration: origin, brand, restaurants and colors. Draft changes are ignored. The partner secret stays in Atlas. Manual revocations remain effective. A temporary failure returns guest_atlas_unavailable; publication remains saved.

Parameters

NameTypeRequiredDescription
deploymentIdstringYesGuest deployment UUID.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "automatic": true,
    "configured": true,
    "status": "ready"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.automaticbooleantrueThe automatic value.
data.configuredbooleantrueThe configured value.
data.statusstring"ready"HTTP status code returned by the API.

Each configured restaurant may opt in with orderingVersion: 'v1' | 'v2' and atlasAutomatic: true. The API replaces the corresponding URL with its own environment-specific value when saving or publishing. Existing configurations without these properties retain their custom URLs unchanged. Ordering enters the native consumption-mode flow; no sit-in mode is forced. The configured restaurant is bound by the signed Guest SSO context. Atlas tokens remain short-lived and are generated only for an authenticated guest, never persisted in configuration.

The server origin overrides are GUEST_APP_ORDERING_V1_ORIGIN, GUEST_APP_ORDERING_V2_ORIGIN and GUEST_APP_ATLAS_ORIGIN. They accept HTTPS origins only. Atlas is provisioned automatically from the current publication. The setup response reports live partner status; secrets remain encrypted in Atlas. No per-company environment profile is needed. Unpublished drafts never alter the live partner scope.

Templates include sodexo and essentiel version 1, alongside Maison, Pulse, Market and Signature. Sodexo has bundled official brand assets: an empty logoUrl uses its reference logo; a configured HTTPS logo still overrides it. Missing template references still resolve to Maison v1. Publishing and restoring keep the same optimistic revision and tenant checks; no SQL migration is needed.

The Guest-only v1 adapter uses the ordinary OAuth JWT exchange, waits for authenticated identity and verifies server-provided Guest metadata before rendering. It does not invoke the historical balance/email update flow. It strips authentication parameters, preserves the strict parent-message origin/source checks and uses single-use server handoffs for external payment restoration. The legacy engine retains its native language catalog with an English fallback when the requested translation is unavailable; Guest controls and the Essentiel renderer support the ten Guest languages.

Published menus and identified ratings

Weekly menu occurrences are distinct from orderable catalogue products. A publication snapshots the dish content. Dates use the restaurant time zone. A rating opens at the service start and closes at the end of local day J+7. There is one editable integer score per customer and occurrence, no comment and no purchase-proof requirement. A diner receives only their own ratings; Administrators and owning brand users access aggregate counts, score distribution and individual ratings with the guest’s name and customer ID. Names are never exposed to other diners; deleted or out-of-brand customer identities are returned as null. The Guest rating form explains this visibility before submission. Explicit closures are returned as closedServices entries with date and service (lunch or dinner), separately from missing menu data. A service cannot be both closed and populated with dishes; a closure-only week can be published.

GET/guest-app/restaurants/{restaurantId}/menusRead published menu weeks

Customer-only. Restaurant must belong to the customer brand and the published deployment. The inclusive date range may span at most 63 days.

Parameters

NameTypeRequiredDescription
restaurantIdintegerYesConfigured restaurant.
fromstringYesFirst local date, YYYY-MM-DD.
tostringYesLast local date, YYYY-MM-DD.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": [
    {
      "menuWeekId": "11111111-1111-4111-8111-111111111111",
      "restaurantId": 200,
      "weekStart": "2026-09-07",
      "title": "Weekly menu",
      "revision": 1,
      "status": "published",
      "publishedVersion": 1,
      "closedServices": [
        {
          "date": "2026-09-07",
          "service": "dinner"
        }
      ],
      "occurrences": [
        {
          "occurrenceId": "22222222-2222-4222-8222-222222222222",
          "dishId": "33333333-3333-4333-8333-333333333333",
          "date": "2026-09-07",
          "service": "lunch",
          "serviceStartTime": "11:30",
          "position": 0,
          "ratingOpensAt": "2026-09-07T09:30:00.000Z",
          "ratingClosesAt": "2026-09-14T21:59:59.999Z",
          "dish": {
            "name": "Vegetable bowl",
            "description": "",
            "category": "main",
            "sourceLocale": "en",
            "allergens": [
              "sesame"
            ],
            "labels": [],
            "nutriScore": "B",
            "translations": {
              "fr": {
                "name": "Assiette de légumes",
                "description": ""
              }
            }
          }
        }
      ]
    }
  ]
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataarray[…]Endpoint-specific response payload.
data[]object{…}Endpoint-specific response payload.
data[].menuWeekIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated menu week.
data[].restaurantIdinteger200Identifier of the restaurant.
data[].weekStartstring"2026-09-07"The week start value.
data[].titlestring"Weekly menu"The title value.
data[].revisioninteger1The revision value.
data[].statusstring"published"HTTP status code returned by the API.
data[].publishedVersioninteger1The published version value.
data[].closedServicesarray[…]List of closed services entries.
data[].closedServices[]object{…}Object containing closed services fields.
data[].closedServices[].datestring"2026-09-07"The date value.
data[].closedServices[].servicestring"dinner"The service value.
data[].occurrencesarray[…]List of occurrences entries.
data[].occurrences[]object{…}Object containing occurrences fields.
data[].occurrences[].occurrenceIdstring"22222222-2222-4222-8222-222222222222"Identifier of the associated occurrence.
data[].occurrences[].dishIdstring"33333333-3333-4333-8333-333333333333"Identifier of the associated dish.
data[].occurrences[].datestring"2026-09-07"The date value.
data[].occurrences[].servicestring"lunch"The service value.
data[].occurrences[].serviceStartTimestring"11:30"The service start time value.
data[].occurrences[].positioninteger0The position value.
data[].occurrences[].ratingOpensAtstring"2026-09-07T09:30:00.000Z"Date or timestamp for rating opens.
data[].occurrences[].ratingClosesAtstring"2026-09-14T21:59:59.999Z"Date or timestamp for rating closes.
data[].occurrences[].dishobject{…}Object containing dish fields.
data[].occurrences[].dish.namestring"Vegetable bowl"The name value.
data[].occurrences[].dish.descriptionstring""The description value.
data[].occurrences[].dish.categorystring"main"The category value.
data[].occurrences[].dish.sourceLocalestring"en"The source locale value.
data[].occurrences[].dish.allergensarray[…]List of allergens entries.
data[].occurrences[].dish.allergens[]string"sesame"The allergens value.
data[].occurrences[].dish.labelsarray[]List of labels entries.
data[].occurrences[].dish.nutriScorestring"B"The nutri score value.
data[].occurrences[].dish.translationsobject{…}Object containing translations fields.
data[].occurrences[].dish.translations.frobject{…}Object containing fr fields.
data[].occurrences[].dish.translations.fr.namestring"Assiette de légumes"The name value.
data[].occurrences[].dish.translations.fr.descriptionstring""The description value.

GET/guest-app/restaurants/{restaurantId}/ratingsRead my dish ratings

Returns the authenticated customer’s own ratings for the allowed restaurant, including ratings whose edit window has closed.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": [
    {
      "occurrenceId": "22222222-2222-4222-8222-222222222222",
      "score": 4,
      "updatedAt": "2026-09-07T12:00:00.000Z"
    }
  ]
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataarray[…]Endpoint-specific response payload.
data[]object{…}Endpoint-specific response payload.
data[].occurrenceIdstring"22222222-2222-4222-8222-222222222222"Identifier of the associated occurrence.
data[].scoreinteger4The score value.
data[].updatedAtstring"2026-09-07T12:00:00.000Z"Timestamp when this resource was last updated.

PUT/guest-app/occurrences/{occurrenceId}/ratingCreate or update my private score

Customer-only. Tenant, published occurrence and rating window are checked on the server. Repeating the same occurrence updates the existing score.

Request Body

json
{
  "score": 4
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
scoreinteger4The score value.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "occurrenceId": "22222222-2222-4222-8222-222222222222",
    "score": 4,
    "updatedAt": "2026-09-07T12:00:00.000Z"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.occurrenceIdstring"22222222-2222-4222-8222-222222222222"Identifier of the associated occurrence.
data.scoreinteger4The score value.
data.updatedAtstring"2026-09-07T12:00:00.000Z"Timestamp when this resource was last updated.

Published dishes may include labels, allergens and nutriScore. Recognized quality-label identifiers are label-rouge, aoc, aop, igp, agriculture-biologique and bienvenue-a-la-ferme. Standard allergen identifiers are gluten, crustaceans, eggs, fish, peanuts, soy, milk, nuts, celery, mustard, sesame, sulphites, lupin, molluscs. Historical names and custom text remain accepted. Empty or missing allergens mean that information is not provided, not that a dish is allergen-free. The optional Nutri-Score is an operator-supplied uppercase grade A–E; it is never inferred from the recipe or photograph. On update, omission preserves an existing grade, while null clears it. These fields are captured in each published occurrence; saving the dish library does not rewrite an already published menu.

Restaurant menu editor

Menu editing and aggregate reporting allow administrators and owning brand users with backoffice bearer tokens. Brand-group and restaurant roles cannot access these routes, including through direct API calls. Restaurant and brand scope checks remain in place. Draft writes use revisions; a conflicting edit returns 409. Republish corrections retain occurrence IDs and existing ratings, while duplicating a week creates new dated occurrences.

OperationEndpointInput
GET/guest-app/admin/dishesbrandId; optional restaurantId
POST/guest-app/admin/dishesbrandId; optional restaurantId; dish name, description, category, imageUrl, allergens, labels, optional nutriScore, sourceLocale, translations
PUT/guest-app/admin/dishes/{dishId}Dish fields; optional restaurantId query
DELETE/guest-app/admin/dishes/{dishId}Archive; optional restaurantId query
GET/guest-app/admin/restaurants/{restaurantId}/menu-weeksfrom, to
PUT/guest-app/admin/restaurants/{restaurantId}/menu-weeks/{weekStart}revision on update, title, occurrences (at most 200), optional closedServices
POST/guest-app/admin/restaurants/{restaurantId}/menu-weeks/{weekStart}/publishrevision
POST/guest-app/admin/restaurants/{restaurantId}/menu-weeks/{weekStart}/duplicatetargetWeekStart
GET/guest-app/admin/restaurants/{restaurantId}/rating-detailsfrom, to (service dates, maximum 94 days); optional dishId, offset (default 0, maximum 100000), limit (default 50, maximum 100). Returns {ratings:[{occurrenceId,dishId,dishName,date,service,score,updatedAt,customer:{customerId,firstName,lastName}|null}],total,offset,limit}. Admin and owning brand only. No email, badge, financial data or response caching.
GET/guest-app/admin/restaurants/{restaurantId}/rating-aggregatesfrom, to; returns occurrenceId, dishId, count, average, distribution

Each occurrence contains dishId, date, service (lunch or dinner), serviceStartTime and position; retain occurrenceId when correcting an existing occurrence. Week starts are Mondays in YYYY-MM-DD format. Omitting closedServices on an edit preserves existing closures; send [] to clear them. Duplicating a week shifts closure dates as well as occurrences.

Scheduled communications

GET/guest-app/admin/deployments/{deploymentId}/communications/{communicationId}Open a communication directly

Administrator or owning brand user. Returns the same draft/published/revision/status shape as a list item, independent of pagination. Both identifiers must be UUIDs. Ownership is checked before reading, and the message must belong to this deployment; a missing or differently-scoped message returns 404. The backoffice uses this endpoint for reloadable edit/duplicate links.

Communications belong to an application and are published independently from its configuration. Administrators and owning brand users can edit them. Saving changes only the draft; the published snapshot remains visible until explicit republication. Every update, publication and withdrawal requires the current revision; stale revisions return 409. Duplication creates a new draft through POST.

GET/guest-app/brands/{brandId}/statusRead Guest App installation status

Administrators and owning brand users. Installed means at least one deployment exists, including unpublished applications and domains awaiting DNS. No channel activation or deactivation mutation is used.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "installed": true
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.installedbooleantrueThe installed value.

GET/guest-app/admin/deployments/{deploymentId}/communicationsList communications

Administrators and owning brand users. Optional search (title, up to 160 characters) and opaque cursor. Returns up to 50 items plus nextCursor. Status is draft, scheduled, active, ended or withdrawn; a published item may also have pending draft changes.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "items": [],
    "nextCursor": null
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.itemsarray[]List of items entries.
data.nextCursornullnullCursor to use when retrieving the next page.

POST/guest-app/admin/deployments/{deploymentId}/communicationsCreate a communication draft

The application must have a saved configuration. All targeted restaurants and translation locales must belong to it. Use PUT on /communications/{communicationId} with {content, revision} to edit.

Request Body

json
{
  "content": {
    "title": "Cooking workshop",
    "body": "<p>Meet our <strong>chef</strong>.</p>",
    "sourceLocale": "en",
    "translations": {
      "fr": {
        "title": "Atelier cuisine",
        "body": "<p>Rencontrez notre chef.</p>"
      }
    },
    "audience": "restaurants",
    "restaurantIds": [
      200
    ],
    "startsLocal": "2026-10-01T12:00",
    "endsLocal": "2026-10-01T14:00",
    "timeZone": "Europe/Paris"
  }
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
contentobject{…}Object containing content fields.
content.titlestring"Cooking workshop"The title value.
content.bodystring"<p>Meet our <strong>chef</strong>.</p>"The body value.
content.sourceLocalestring"en"The source locale value.
content.translationsobject{…}Object containing translations fields.
content.translations.frobject{…}Object containing fr fields.
content.translations.fr.titlestring"Atelier cuisine"The title value.
content.translations.fr.bodystring"<p>Rencontrez notre chef.</p>"The body value.
content.audiencestring"restaurants"The audience value.
content.restaurantIdsarray[…]List of restaurant ids entries.
content.restaurantIds[]integer200The restaurant ids value.
content.startsLocalstring"2026-10-01T12:00"The starts local value.
content.endsLocalstring"2026-10-01T14:00"The ends local value.
content.timeZonestring"Europe/Paris"The time zone value.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "communicationId": "33333333-3333-4333-8333-333333333333",
    "deploymentId": "11111111-1111-4111-8111-111111111111",
    "revision": 1,
    "status": "draft",
    "published": null
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.communicationIdstring"33333333-3333-4333-8333-333333333333"Identifier of the associated communication.
data.deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
data.revisioninteger1The revision value.
data.statusstring"draft"HTTP status code returned by the API.
data.publishednullnullThe published value.

PUT/guest-app/admin/deployments/{deploymentId}/communications/{communicationId}Save a communication draft

Same content as creation, plus revision. Draft and published snapshots are separate; editing never changes the currently visible message.

POST/guest-app/admin/deployments/{deploymentId}/communications/{communicationId}/publicationPublish or withdraw a communication

Set withdrawn=true to remove a message immediately; withdrawal remains possible after a targeted restaurant is removed. At most 100 non-expired published communications may coexist per application. Publication does not require a scheduling job.

Request Body

json
{
  "revision": 1,
  "withdrawn": false
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
revisioninteger1The revision value.
withdrawnbooleanfalseThe withdrawn value.

Title: 160 characters; body: 20,000 UTF-8 bytes after sanitization; optional buttonLabel: 80 characters, required with buttonUrl. Optional imageUrl and buttonUrl accept HTTPS only. Rich text permits paragraphs, bold, italic, lists and links; scripts, styles, embeds and unsafe protocols are removed server-side. Translations use the same text fields. Missing translations fall back to the complete source text with contentLocale identifying its language.

Enter startsLocal and endsLocal as YYYY-MM-DDTHH:mm in the application's IANA timeZone. The server stores UTC startsAt/endsAt and retains the original zone when editing. Nonexistent or ambiguous local times are rejected. Start is inclusive, end exclusive and strictly later than start. Application-wide audience ignores restaurantIds; restaurant targeting requires a nonempty permitted selection.

GET/guest-app/deployments/{deploymentId}/communicationsRead active diner communications

Customer-only. Requires restaurantId and locale. The deployment must be published and belong to the customer brand; restaurant membership is enforced. Returns only published, non-withdrawn messages active at server time for the selected restaurant, most recently started first. Removed restaurants are excluded. The Guest BFF derives deploymentId from its authenticated session and exposes GET /api/communications; responses are never PWA-cached.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "serverNow": "2026-10-01T10:00:00.000Z",
    "nextChangeAt": "2026-10-01T12:00:00.000Z",
    "restaurantScoped": true,
    "items": [
      {
        "communicationId": "33333333-3333-4333-8333-333333333333",
        "title": "Cooking workshop",
        "body": "<p>Meet our chef.</p>",
        "contentLocale": "en",
        "startsAt": "2026-10-01T10:00:00.000Z",
        "endsAt": "2026-10-01T12:00:00.000Z"
      }
    ]
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.serverNowstring"2026-10-01T10:00:00.000Z"The server now value.
data.nextChangeAtstring"2026-10-01T12:00:00.000Z"Date or timestamp for next change.
data.restaurantScopedbooleantrueThe restaurant scoped value.
data.itemsarray[…]List of items entries.
data.items[]object{…}Object containing items fields.
data.items[].communicationIdstring"33333333-3333-4333-8333-333333333333"Identifier of the associated communication.
data.items[].titlestring"Cooking workshop"The title value.
data.items[].bodystring"<p>Meet our chef.</p>"The body value.
data.items[].contentLocalestring"en"The content locale value.
data.items[].startsAtstring"2026-10-01T10:00:00.000Z"Date or timestamp for starts.
data.items[].endsAtstring"2026-10-01T12:00:00.000Z"Date or timestamp for ends.

All built-in modules and custom modules can be reordered. The optional features.badge and features.balance flags default to enabled for historical configurations. moduleTitles provides optional plain-text display names for built-ins. moduleOrder accepts badge, balance, communications, menus, ratings, ordering, topUp, automaticTopUp, wallet, attendance, registration and custom:<moduleId>. Registration remains on the sign-in screen; ratings require menus. Existing technical capabilities and editor permissions are unchanged.

The home screen shows three active cards, then Show more, in the configured moduleOrder. Badge and balance have no fixed position. restaurantScoped keeps restaurant selection available for active targeted messages. nextChangeAt covers upcoming starts and ends across the application; refresh at this instant using serverNow, every minute while visible, after restaurant changes and on return to the app. No pushes, emails or automatic popups are sent. Preview uses fictitious messages only.

Durable Stripe recharge

Create one UUID per deliberate recharge and retain it through retries. The API stores immutable customer, card, amount, currency and Stripe-account terms before provider calls. It commits the PaymentIntent ID before confirming any charge. Successful receipt verification credits the Innovorder ledger once under a database lock. Polling reconciles the same operation; it does not create a new payment. Automatic reload continues to use the existing customer automatic-reload settings where enabled; CASH and POSTPAYMENT accounts are not eligible. An upstream null automatic-reload configuration means inactive; a configuration object means active, and the Guest BFF normalizes this explicitly.

Combined sales and wallet receipts

GET/guest-app/deployments/:deploymentId/historyRead the diner’s combined receipt history

Customer only. Groups sales with explicitly linked ewallet movements, retaining sales without wallet payments and standalone credits/debits. Ambiguous references are not merged. Sorted by latest activity, then type and identifier. Sale totals and wallet amounts remain separate; all money is in minor units.

Parameters

NameTypeRequiredDescription
deploymentIdstringYesPublished Guest deployment UUID.
cursorstringNoOpaque nextCursor from the previous page.
limitnumberNo1–50 groups; default 20.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "entries": [
      {
        "key": "sale:123",
        "kind": "sale",
        "occurredAt": "2026-09-16T12:00:00Z",
        "saleAt": "2026-09-15T12:00:00Z",
        "restaurantName": "Main restaurant",
        "timeZone": "Europe/Paris",
        "currency": "EUR",
        "amount": 1000,
        "status": "PAID",
        "number": "T123",
        "walletCount": 1,
        "walletType": null
      }
    ],
    "nextCursor": null
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.entriesarray[…]List of entries entries.
data.entries[]object{…}Object containing entries fields.
data.entries[].keystring"sale:123"The key value.
data.entries[].kindstring"sale"The kind value.
data.entries[].occurredAtstring"2026-09-16T12:00:00Z"Date or timestamp for occurred.
data.entries[].saleAtstring"2026-09-15T12:00:00Z"Date or timestamp for sale.
data.entries[].restaurantNamestring"Main restaurant"The restaurant name value.
data.entries[].timeZonestring"Europe/Paris"The time zone value.
data.entries[].currencystring"EUR"ISO 4217 currency code.
data.entries[].amountinteger1000The amount value.
data.entries[].statusstring"PAID"HTTP status code returned by the API.
data.entries[].numberstring"T123"The number value.
data.entries[].walletCountinteger1The wallet count value.
data.entries[].walletTypenullnullThe wallet type value.
data.nextCursornullnullCursor to use when retrieving the next page.

GET/guest-app/deployments/:deploymentId/history/:entryIdRead receipt details and associated wallet movements

Revalidates customer, brand and published restaurant scope. Includes item lines, recorded totals, adjustments and every associated wallet movement with its own signed amount, currency, date and status. Missing/foreign entries return 404. No payment secrets or other diners’ data.

Parameters

NameTypeRequiredDescription
deploymentIdstringYesPublished deployment UUID.
entryIdstringYesKey returned by the list: sale:<id> or wallet:<id>.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "key": "wallet:456",
    "kind": "wallet",
    "amount": 2000,
    "currency": "EUR",
    "lines": [],
    "adjustments": [],
    "totals": null,
    "transactions": [
      {
        "id": "456",
        "amount": 2000,
        "currency": "EUR",
        "type": "1",
        "status": "1",
        "occurredAt": "2026-09-16T12:00:00Z",
        "paymentOrigin": "4"
      }
    ]
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.keystring"wallet:456"The key value.
data.kindstring"wallet"The kind value.
data.amountinteger2000The amount value.
data.currencystring"EUR"ISO 4217 currency code.
data.linesarray[]List of lines entries.
data.adjustmentsarray[]List of adjustments entries.
data.totalsnullnullThe totals value.
data.transactionsarray[…]List of transactions entries.
data.transactions[]object{…}Object containing transactions fields.
data.transactions[].idstring"456"The id value.
data.transactions[].amountinteger2000The amount value.
data.transactions[].currencystring"EUR"ISO 4217 currency code.
data.transactions[].typestring"1"The type value.
data.transactions[].statusstring"1"HTTP status code returned by the API.
data.transactions[].occurredAtstring"2026-09-16T12:00:00Z"Date or timestamp for occurred.
data.transactions[].paymentOriginstring"4"The payment origin value.

The browser uses /api/history and /api/history/:entryId; the BFF derives tenant and customer from the session. Responses are no-store. The preview uses fictitious data without financial API calls. Failed reads stay distinct from empty history.

POST/guest-app/cardsCreate or finalize a leased card setup

Customer-only. Generate leaseId once on the client and reuse it for retries. deploymentId, name and retain belong to that lease. Without cardToken the response provides the Stripe SetupIntent client secret. After Stripe setup succeeds, repeat the same request with its pm_ payment-method ID as cardToken; the response is the saved Card.

Request Body

json
{
  "deploymentId": "11111111-1111-4111-8111-111111111111",
  "name": "Personal card",
  "leaseId": "44444444-4444-4444-8444-444444444444",
  "retain": false
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
namestring"Personal card"The name value.
leaseIdstring"44444444-4444-4444-8444-444444444444"Identifier of the associated lease.
retainbooleanfalseThe retain value.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "leaseId": "44444444-4444-4444-8444-444444444444",
    "client_secret": "seti_example_secret_redacted"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.leaseIdstring"44444444-4444-4444-8444-444444444444"Identifier of the associated lease.
data.client_secretstring"seti_example_secret_redacted"The client secret value.

A successful finalization returns the Card (including cardId) used for recharge. With retain=false, an abandoned temporary card lease is eligible for cleanup after one hour, except while a nonterminal recharge references that card. The same lease protects setup and attachment retries; do not issue a replacement lease after an ambiguous response. retain=true explicitly saves the payment method. The Guest BFF exposes this as POST /api/cards and derives deploymentId from the session hostname. The interface prevents an unmounted card form from starting a recharge.

POST/guest-app/topupsStart or recover a Stripe recharge

Customer-only. A modern saved Stripe PaymentMethod belonging to this customer is required. Amount is an integer in the configured currency’s minor unit. Reusing operationId with a different card, amount, deployment or transient choice returns 409.

Request Body

json
{
  "operationId": "33333333-3333-4333-8333-333333333333",
  "deploymentId": "11111111-1111-4111-8111-111111111111",
  "cardId": 300,
  "amount": 2000,
  "transient": false
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
operationIdstring"33333333-3333-4333-8333-333333333333"Identifier of the associated operation.
deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
cardIdinteger300Identifier of the associated card.
amountinteger2000The amount value.
transientbooleanfalseThe transient value.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "operationId": "33333333-3333-4333-8333-333333333333",
    "status": "requires_action",
    "paymentIntentId": "pi_example",
    "clientSecret": "pi_example_secret_redacted"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.operationIdstring"33333333-3333-4333-8333-333333333333"Identifier of the associated operation.
data.statusstring"requires_action"HTTP status code returned by the API.
data.paymentIntentIdstring"pi_example"Identifier of the associated payment intent.
data.clientSecretstring"pi_example_secret_redacted"The client secret value.

GET/guest-app/topups/{operationId}Reconcile the recharge status

Returns pending, requires_action, succeeded or failed. The operation must belong to the caller. Only requires_action exposes clientSecret. Never infer success from a browser redirect or a 202 response.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "operationId": "33333333-3333-4333-8333-333333333333",
    "status": "succeeded",
    "paymentIntentId": "pi_example",
    "transactionId": 400
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.operationIdstring"33333333-3333-4333-8333-333333333333"Identifier of the associated operation.
data.statusstring"succeeded"HTTP status code returned by the API.
data.paymentIntentIdstring"pi_example"Identifier of the associated payment intent.
data.transactionIdinteger400Identifier of the transaction.

POST/guest-app/topups/{operationId}/resumeResume the same intent after customer authentication

No customer-supplied amount, card or intent is trusted on resume. The API reloads the stored operation and retrieves its Stripe intent. A five-minute reconciliation worker also recovers interrupted operations.

Request Body

json
{}

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "operationId": "33333333-3333-4333-8333-333333333333",
    "status": "succeeded",
    "transactionId": 400
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.operationIdstring"33333333-3333-4333-8333-333333333333"Identifier of the associated operation.
data.statusstring"succeeded"HTTP status code returned by the API.
data.transactionIdinteger400Identifier of the transaction.

The browser BFF exposes corresponding /api/topups routes and maps statuses to pending, action and confirmed. Temporary card leases are scoped to this customer; after a terminal payment outcome they are disabled and detached, with durable retry if cleanup fails. Abandoned temporary leases are cleaned after one hour unless an operation is still pending. Provider secrets remain exclusively in the API runtime.

Ordering and Atlas launch sessions

POST/guest-app/ssoLaunch Ordering with a one-use customer session

The target restaurant must be configured for this deployment. Returns a one-use JWT URL valid for 60 seconds. Ordering consumes the ticket atomically, resets prior guest state and removes the login token from the address bar.

Request Body

json
{
  "deploymentId": "11111111-1111-4111-8111-111111111111",
  "restaurantId": 200
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
restaurantIdinteger200Identifier of the restaurant.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "url": "https://ordering.example.test/?autoLoginJWT=REDACTED&embedded=true",
    "expiresAt": "2026-09-07T10:01:00.000Z"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.urlstring"https://ordering.example.test/?autoLoginJWT=REDACTED&embedded=true"The url value.
data.expiresAtstring"2026-09-07T10:01:00.000Z"Date or timestamp for expires.

The Guest SSO exchange through /oauth/login also returns data.guestAppContext, containing the verified parentOrigin and restaurantId. Ordering activates its embedded bridge only when those values match the requested context. URL parameters and the referrer alone are never an authorization source. Disabled restaurants keep their historical menus and ratings accessible to authorized diners and Innovorder administrators, but cannot issue a new Ordering session.

POST/guest-app/ordering-handoffMove an authenticated order out of the iframe

Customer-only. The path must remain on the configured Ordering origin. The API stores an untrusted cart snapshot (up to 128 KiB) and returns a fresh one-use SSO URL plus an opaque guestHandoff code. This avoids relying on iframe storage being shared with a top-level browser page.

Request Body

json
{
  "deploymentId": "11111111-1111-4111-8111-111111111111",
  "restaurantId": 200,
  "path": "/payment",
  "cart": {}
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
restaurantIdinteger200Identifier of the restaurant.
pathstring"/payment"The path value.
cartobject{…}Object containing cart fields.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "url": "https://ordering.example.test/payment?autoLoginJWT=REDACTED&guestHandoff=REDACTED",
    "expiresAt": "2026-09-07T10:01:00.000Z",
    "cartExpiresAt": "2026-09-07T10:05:00.000Z"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.urlstring"https://ordering.example.test/payment?autoLoginJWT=REDACTED&guestHandoff=RED…The url value.
data.expiresAtstring"2026-09-07T10:01:00.000Z"Date or timestamp for expires.
data.cartExpiresAtstring"2026-09-07T10:05:00.000Z"Date or timestamp for cart expires.

GET/guest-app/ordering-handoff/{code}Consume the one-use cart handoff after OAuth

Authenticate as the same customer and brand first. The opaque code is valid for five minutes and is consumed once; expired, foreign or already-consumed codes return 404. The fresh SSO ticket has its separate 60-second lifetime. Ordering normalizes the returned cart and requests a new API preview; copied prices and totals are never authoritative.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "cart": {}
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.cartobject{…}Object containing cart fields.

The same-origin browser endpoint is POST /api/ordering-handoff with restaurantId, path and cart. The BFF resolves deploymentId from its authenticated context. Never transfer the bearer token through postMessage.

POST/guest-app/atlas-sessionLaunch the restricted historical-footfall embed

Requires enabled attendance. Atlas synchronizes the managed partner from the current publication and signs the session; missing partners are created automatically. The signed token has one brand, one restaurant, its IANA time zone, audience atlas-embed and features.guestFootfall=true. It cannot access ordinary financial analytics.

Request Body

json
{
  "deploymentId": "11111111-1111-4111-8111-111111111111",
  "restaurantId": 200
}
Request Body Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
deploymentIdstring"11111111-1111-4111-8111-111111111111"Identifier of the associated deployment.
restaurantIdinteger200Identifier of the restaurant.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "url": "https://atlas.example.test/embed/guest-footfall?locale=fr#token=REDACTED",
    "expiresAt": "2026-09-07T10:15:00.000Z"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.urlstring"https://atlas.example.test/embed/guest-footfall?locale=fr#token=REDACTED"The url value.
data.expiresAtstring"2026-09-07T10:15:00.000Z"Date or timestamp for expires.

Atlas shows average ticket counts per 15-minute slot over the previous 28 completed local business dates. All slots use the same observed-day denominator. It is historical information, not live occupancy, unique people or waiting time. An empty history differs from service unavailability. The Guest BFF supplies these launch sessions through /api/ordering-session and /api/atlas-session with a restaurantId; it derives the deployment server-side.

javascript
// For Atlas only; keep the short JWT in the fragment and in memory.
window.addEventListener('message', async (event) => {
  if (event.origin !== atlasOrigin || event.source !== iframe.contentWindow) return;
  if (event.data?.type !== 'atlas:token-refresh-needed') return;
  const { url } = await renewGuestAtlasSession(); // same-origin BFF + CSRF
  const token = new URL(url).hash.slice('#token='.length);
  iframe.contentWindow.postMessage({ type: 'atlas:token', token }, atlasOrigin);
});

Apple and Google Wallet

GET/guest-app/wallet/{deploymentId}/{provider}Add my badge to a device wallet

Customer-only; provider is apple or google. Apple returns application/vnd.apple.pkpass bytes. Google returns an Add to Google Wallet URL. Missing issuer configuration returns 503 guest_wallet_unavailable. A blocked or replaced badge is reconciled by the Wallet worker.

Response

json
{
  "status": 200,
  "code": "guest_app_succeed",
  "message": "Guest App request succeeded",
  "data": {
    "url": "https://pay.google.com/gp/v/save/REDACTED"
  }
}
Response Properties

Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.

PropertyTypeExampleDescription
statusinteger200HTTP status code returned by the API.
codestring"guest_app_succeed"Machine-readable application code for the result.
messagestring"Guest App request succeeded"Human-readable result message. Do not use this value for program logic.
dataobject{…}Endpoint-specific response payload.
data.urlstring"https://pay.google.com/gp/v/save/REDACTED"The url value.

These passes identify a badge; they are not NFC payment credentials and do not replace the POS eligibility check. Apple device registration, updates and signed-pass downloads are handled under /guest-app/wallet/apple/v1using the ApplePass protocol. API deployment profiles contain issuer certificates/private keys on the server only. No private credential is returned by configuration or customer endpoints. The API runtime requires the OpenSSL command-line binary to produce the detached CMS signature; verify this prerequisite in the promoted API image.

Errors and activation

Invalid input is 400; expired customer sessions are 401; unauthorized tenant, restaurant or capability is 403; missing resources are 404; revision/payment-term conflicts and closed rating windows are 409; missing provider configuration or upstream failures are 503. Process the machine-readable error code rather than its translated message. Administrative role denials use 401 with the permission_denied code. Apply all three Guest App API migrations (deployment/menu/ratings/wallet, durable recharge, then handoff/card leases), configure approved provider profiles and validate connected sandbox flows before enabling a deployment.