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.
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 suffix | JWT module | Content |
|---|---|---|
| /transactions | transactions | Sales and wallet history, details and PDF receipts |
| /topup, /cards, /auto-reload | payments | Stripe top-up, saved cards and automatic reload, subject to account eligibility |
| /badge | badge | Badge and Apple/Google Wallet |
| /menus | menus | Weekly menus, restaurants, dish details and private ratings |
| /app/* | app | Full 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.
{
"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.
// 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
<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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| revision | integer | 0 | The revision value. |
| approvalMode | string | "manual" | The approval mode value. |
| advanceDays | integer | 30 | The advance days value. |
| guestCancellationEnabled | boolean | true | The guest cancellation enabled value. |
| cancellationCutoffMinutes | integer | 120 | The cancellation cutoff minutes value. |
| contactEmail | string | "restaurant@example.org" | The contact email value. |
| notifyContact | boolean | false | The notify contact value. |
| location | string | "10 Example Street, Paris" | The location value. |
GET/guest-app/reservations/admin/deployments/{deploymentId}/restaurants/{restaurantId}/slotsList dated slots
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| from | YYYY-MM-DD | Yes | First local service date. |
| to | YYYY-MM-DD | Yes | Last 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| revision | integer | 0 | The revision value. |
| startsLocal | string | "2026-11-12T12:00" | The starts local value. |
| endsLocal | string | "2026-11-12T13:30" | The ends local value. |
| capacity | integer | 30 | The capacity value. |
| maxPartySize | integer | 6 | The max party size value. |
| enabled | boolean | true | The 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| revision | integer | 1 | The revision value. |
| startsLocal | string | "2026-11-12T12:00" | The starts local value. |
| endsLocal | string | "2026-11-12T13:30" | The ends local value. |
| capacity | integer | 30 | The capacity value. |
| maxPartySize | integer | 6 | The max party size value. |
| enabled | boolean | false | The 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
{
"revision": 2
}Request Body Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| revision | integer | 2 | The 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| revision | integer | 1 | The revision value. |
| action | string | "cancel" | The action value. |
| reason | string | "restaurant_closed" | The reason value. |
| comment | string | "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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| slotId | string | "22222222-2222-4222-8222-222222222222" | Identifier of the associated slot. |
| attemptId | string | "33333333-3333-4333-8333-333333333333" | Identifier of the associated attempt. |
| partySize | integer | 2 | The party size value. |
| phone | string | "+33123456789" | Phone number. |
| comment | string | "A high chair if possible." | The comment value. |
| locale | string | "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
{
"confirm": true
}Request Body Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| confirm | boolean | true | The 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
{
"reservationToken": "<private-capability>"
}Request Body Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| reservationToken | string | "<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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| reservationToken | string | "<private-capability>" | The reservation token value. |
| confirm | boolean | true | The 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| moduleId | string | "89bb7181-96b2-45b8-b89a-464414bb3518" | Identifier of the associated module. |
| restaurantId | integer | 200 | Identifier of the restaurant. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.url | string | "https://partner.example.org/app" | The url value. |
| data.token | string | "<short-lived-signed-proof>" | The token value. |
| data.expiresAt | string | "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
| Name | Type | Required | Description |
|---|---|---|---|
| deploymentId | UUID | Yes | Authorized Guest application. |
| entryId | string | Yes | History key, sale:<id> or wallet:<id>. |
| receiptId | string | Yes | Receipt key from the selected history detail. |
Response
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
| Name | Type | Required | Description |
|---|---|---|---|
| hostname | string | Yes | Published hostname, without scheme or path. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| data.brandId | integer | 100 | Identifier of the brand. |
| data.hostname | string | "guests.example.test" | The hostname value. |
| data.name | string | "Example enterprise" | The name value. |
| data.version | integer | 1 | The version value. |
| data.topUpLimits | object | {…} | Object containing top up limits fields. |
| data.topUpLimits.minimumAmount | integer | 100 | The minimum amount value. |
| data.topUpLimits.maximumAmount | integer | 40000 | The maximum amount value. |
| data.configuration | object | {…} | Object containing configuration fields. |
| data.configuration.displayName | string | "Example restaurant" | The display name value. |
| data.configuration.defaultLocale | string | "fr" | The default locale value. |
| data.configuration.locales | array | […] | List of locales entries. |
| data.configuration.locales[] | string | "fr" | The locales value. |
| data.configuration.country | string | "FR" | The country value. |
| data.configuration.currency | string | "EUR" | ISO 4217 currency code. |
| data.configuration.timeZone | string | "Europe/Paris" | The time zone value. |
| data.configuration.restaurants | array | […] | List of restaurants entries. |
| data.configuration.restaurants[] | object | {…} | Object containing restaurants fields. |
| data.configuration.restaurants[].restaurantId | integer | 200 | Identifier of the restaurant. |
| data.configuration.restaurants[].name | string | "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
| Name | Type | Required | Description |
|---|---|---|---|
| deploymentId | string (UUIDv4) | Yes | Published Guest App deployment. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | array | […] | Endpoint-specific response payload. |
| data[] | object | {…} | Endpoint-specific response payload. |
| data[].restaurantId | integer | 200 | Identifier of the restaurant. |
| data[].name | string | "Main restaurant" | The name value. |
| data[].orderingUrl | string | "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
| Name | Type | Required | Description |
|---|---|---|---|
| deploymentId | string (UUID) | Yes | Deployment being configured. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.brand | object | {…} | Object containing brand fields. |
| data.brand.brandId | integer | 100 | Identifier of the brand. |
| data.brand.name | string | "Example company" | The name value. |
| data.brand.brandHash | string | "example" | The brand hash value. |
| data.brand.currency | string | "EUR" | ISO 4217 currency code. |
| data.brand.locale | string | "fr" | The locale value. |
| data.ordering | object | {…} | Object containing ordering fields. |
| data.ordering.configured | boolean | true | The configured value. |
| data.atlas | object | {…} | Object containing atlas fields. |
| data.atlas.automatic | boolean | true | The automatic value. |
| data.atlas.configured | boolean | false | The configured value. |
| data.atlas.status | string | "pending" | HTTP status code returned by the API. |
| data.atlas.embedUrl | string | "https://atlas.example/embed/guest-footfall" | The embed url value. |
| data.restaurants | array | […] | List of restaurants entries. |
| data.restaurants[] | object | {…} | Object containing restaurants fields. |
| data.restaurants[].restaurantId | integer | 200 | Identifier of the restaurant. |
| data.restaurants[].name | string | "Main restaurant" | The name value. |
| data.restaurants[].orderingUrls | object | {…} | Object containing ordering urls fields. |
| data.restaurants[].orderingUrls.v1 | string | "https://ordering-v1.example/home?brandId=100" | The v1 value. |
| data.restaurants[].orderingUrls.v2 | string | "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
| Name | Type | Required | Description |
|---|---|---|---|
| deploymentId | UUID | Yes | Deployment identifier. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.hostname | string | "lunch.customer.fr" | The hostname value. |
| data.revision | integer | 1 | The revision value. |
| data.state | string | "dns_pending" | The state value. |
| data.ready | boolean | false | The ready value. |
| data.legacy | boolean | false | The legacy value. |
| data.editable | boolean | true | The editable value. |
| data.published | boolean | false | The published value. |
| data.dnsState | string | "missing" | The dns state value. |
| data.certificateState | string | "PROVISIONING" | The certificate state value. |
| data.checkedAt | null | null | Date or timestamp for checked. |
| data.nextCheckAt | string | "2026-09-18T12:00:00Z" | Date or timestamp for next check. |
| data.error | string | "dns_missing" | The error value. |
| data.records | array | […] | List of records entries. |
| data.records[] | object | {…} | Object containing records fields. |
| data.records[].type | string | "A" | The type value. |
| data.records[].name | string | "lunch.customer.fr" | The name value. |
| data.records[].relativeName | string | "lunch" | The relative name value. |
| data.records[].value | string | "34.1.2.3" | The value value. |
| data.records[].purpose | string | "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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.scheduled | boolean | true | The 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| hostname | string | "correct.customer.fr" | The hostname value. |
| revision | integer | 1 | The revision value. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| data.hostname | string | "correct.customer.fr" | The hostname value. |
| data.revision | integer | 2 | The 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
| Name | Type | Required | Description |
|---|---|---|---|
| deploymentId | string | Yes | Guest deployment UUID. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.automatic | boolean | true | The automatic value. |
| data.configured | boolean | true | The configured value. |
| data.status | string | "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
| Name | Type | Required | Description |
|---|---|---|---|
| restaurantId | integer | Yes | Configured restaurant. |
| from | string | Yes | First local date, YYYY-MM-DD. |
| to | string | Yes | Last local date, YYYY-MM-DD. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | array | […] | Endpoint-specific response payload. |
| data[] | object | {…} | Endpoint-specific response payload. |
| data[].menuWeekId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated menu week. |
| data[].restaurantId | integer | 200 | Identifier of the restaurant. |
| data[].weekStart | string | "2026-09-07" | The week start value. |
| data[].title | string | "Weekly menu" | The title value. |
| data[].revision | integer | 1 | The revision value. |
| data[].status | string | "published" | HTTP status code returned by the API. |
| data[].publishedVersion | integer | 1 | The published version value. |
| data[].closedServices | array | […] | List of closed services entries. |
| data[].closedServices[] | object | {…} | Object containing closed services fields. |
| data[].closedServices[].date | string | "2026-09-07" | The date value. |
| data[].closedServices[].service | string | "dinner" | The service value. |
| data[].occurrences | array | […] | List of occurrences entries. |
| data[].occurrences[] | object | {…} | Object containing occurrences fields. |
| data[].occurrences[].occurrenceId | string | "22222222-2222-4222-8222-222222222222" | Identifier of the associated occurrence. |
| data[].occurrences[].dishId | string | "33333333-3333-4333-8333-333333333333" | Identifier of the associated dish. |
| data[].occurrences[].date | string | "2026-09-07" | The date value. |
| data[].occurrences[].service | string | "lunch" | The service value. |
| data[].occurrences[].serviceStartTime | string | "11:30" | The service start time value. |
| data[].occurrences[].position | integer | 0 | The position value. |
| data[].occurrences[].ratingOpensAt | string | "2026-09-07T09:30:00.000Z" | Date or timestamp for rating opens. |
| data[].occurrences[].ratingClosesAt | string | "2026-09-14T21:59:59.999Z" | Date or timestamp for rating closes. |
| data[].occurrences[].dish | object | {…} | Object containing dish fields. |
| data[].occurrences[].dish.name | string | "Vegetable bowl" | The name value. |
| data[].occurrences[].dish.description | string | "" | The description value. |
| data[].occurrences[].dish.category | string | "main" | The category value. |
| data[].occurrences[].dish.sourceLocale | string | "en" | The source locale value. |
| data[].occurrences[].dish.allergens | array | […] | List of allergens entries. |
| data[].occurrences[].dish.allergens[] | string | "sesame" | The allergens value. |
| data[].occurrences[].dish.labels | array | [] | List of labels entries. |
| data[].occurrences[].dish.nutriScore | string | "B" | The nutri score value. |
| data[].occurrences[].dish.translations | object | {…} | Object containing translations fields. |
| data[].occurrences[].dish.translations.fr | object | {…} | Object containing fr fields. |
| data[].occurrences[].dish.translations.fr.name | string | "Assiette de légumes" | The name value. |
| data[].occurrences[].dish.translations.fr.description | string | "" | 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | array | […] | Endpoint-specific response payload. |
| data[] | object | {…} | Endpoint-specific response payload. |
| data[].occurrenceId | string | "22222222-2222-4222-8222-222222222222" | Identifier of the associated occurrence. |
| data[].score | integer | 4 | The score value. |
| data[].updatedAt | string | "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
{
"score": 4
}Request Body Properties
Every field in the example is listed below. Explicit requiredness is shown when the endpoint contract defines it.
| Property | Type | Example | Description |
|---|---|---|---|
| score | integer | 4 | The score value. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.occurrenceId | string | "22222222-2222-4222-8222-222222222222" | Identifier of the associated occurrence. |
| data.score | integer | 4 | The score value. |
| data.updatedAt | string | "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.
| Operation | Endpoint | Input |
|---|---|---|
| GET | /guest-app/admin/dishes | brandId; optional restaurantId |
| POST | /guest-app/admin/dishes | brandId; 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-weeks | from, 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}/publish | revision |
| POST | /guest-app/admin/restaurants/{restaurantId}/menu-weeks/{weekStart}/duplicate | targetWeekStart |
| GET | /guest-app/admin/restaurants/{restaurantId}/rating-details | from, 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-aggregates | from, 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.installed | boolean | true | The 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.items | array | [] | List of items entries. |
| data.nextCursor | null | null | Cursor 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| content | object | {…} | Object containing content fields. |
| content.title | string | "Cooking workshop" | The title value. |
| content.body | string | "<p>Meet our <strong>chef</strong>.</p>" | The body value. |
| content.sourceLocale | string | "en" | The source locale value. |
| content.translations | object | {…} | Object containing translations fields. |
| content.translations.fr | object | {…} | Object containing fr fields. |
| content.translations.fr.title | string | "Atelier cuisine" | The title value. |
| content.translations.fr.body | string | "<p>Rencontrez notre chef.</p>" | The body value. |
| content.audience | string | "restaurants" | The audience value. |
| content.restaurantIds | array | […] | List of restaurant ids entries. |
| content.restaurantIds[] | integer | 200 | The restaurant ids value. |
| content.startsLocal | string | "2026-10-01T12:00" | The starts local value. |
| content.endsLocal | string | "2026-10-01T14:00" | The ends local value. |
| content.timeZone | string | "Europe/Paris" | The time zone value. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.communicationId | string | "33333333-3333-4333-8333-333333333333" | Identifier of the associated communication. |
| data.deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| data.revision | integer | 1 | The revision value. |
| data.status | string | "draft" | HTTP status code returned by the API. |
| data.published | null | null | The 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| revision | integer | 1 | The revision value. |
| withdrawn | boolean | false | The 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.serverNow | string | "2026-10-01T10:00:00.000Z" | The server now value. |
| data.nextChangeAt | string | "2026-10-01T12:00:00.000Z" | Date or timestamp for next change. |
| data.restaurantScoped | boolean | true | The restaurant scoped value. |
| data.items | array | […] | List of items entries. |
| data.items[] | object | {…} | Object containing items fields. |
| data.items[].communicationId | string | "33333333-3333-4333-8333-333333333333" | Identifier of the associated communication. |
| data.items[].title | string | "Cooking workshop" | The title value. |
| data.items[].body | string | "<p>Meet our chef.</p>" | The body value. |
| data.items[].contentLocale | string | "en" | The content locale value. |
| data.items[].startsAt | string | "2026-10-01T10:00:00.000Z" | Date or timestamp for starts. |
| data.items[].endsAt | string | "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
| Name | Type | Required | Description |
|---|---|---|---|
| deploymentId | string | Yes | Published Guest deployment UUID. |
| cursor | string | No | Opaque nextCursor from the previous page. |
| limit | number | No | 1–50 groups; default 20. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.entries | array | […] | List of entries entries. |
| data.entries[] | object | {…} | Object containing entries fields. |
| data.entries[].key | string | "sale:123" | The key value. |
| data.entries[].kind | string | "sale" | The kind value. |
| data.entries[].occurredAt | string | "2026-09-16T12:00:00Z" | Date or timestamp for occurred. |
| data.entries[].saleAt | string | "2026-09-15T12:00:00Z" | Date or timestamp for sale. |
| data.entries[].restaurantName | string | "Main restaurant" | The restaurant name value. |
| data.entries[].timeZone | string | "Europe/Paris" | The time zone value. |
| data.entries[].currency | string | "EUR" | ISO 4217 currency code. |
| data.entries[].amount | integer | 1000 | The amount value. |
| data.entries[].status | string | "PAID" | HTTP status code returned by the API. |
| data.entries[].number | string | "T123" | The number value. |
| data.entries[].walletCount | integer | 1 | The wallet count value. |
| data.entries[].walletType | null | null | The wallet type value. |
| data.nextCursor | null | null | Cursor 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
| Name | Type | Required | Description |
|---|---|---|---|
| deploymentId | string | Yes | Published deployment UUID. |
| entryId | string | Yes | Key returned by the list: sale:<id> or wallet:<id>. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.key | string | "wallet:456" | The key value. |
| data.kind | string | "wallet" | The kind value. |
| data.amount | integer | 2000 | The amount value. |
| data.currency | string | "EUR" | ISO 4217 currency code. |
| data.lines | array | [] | List of lines entries. |
| data.adjustments | array | [] | List of adjustments entries. |
| data.totals | null | null | The totals value. |
| data.transactions | array | […] | List of transactions entries. |
| data.transactions[] | object | {…} | Object containing transactions fields. |
| data.transactions[].id | string | "456" | The id value. |
| data.transactions[].amount | integer | 2000 | The amount value. |
| data.transactions[].currency | string | "EUR" | ISO 4217 currency code. |
| data.transactions[].type | string | "1" | The type value. |
| data.transactions[].status | string | "1" | HTTP status code returned by the API. |
| data.transactions[].occurredAt | string | "2026-09-16T12:00:00Z" | Date or timestamp for occurred. |
| data.transactions[].paymentOrigin | string | "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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| name | string | "Personal card" | The name value. |
| leaseId | string | "44444444-4444-4444-8444-444444444444" | Identifier of the associated lease. |
| retain | boolean | false | The retain value. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.leaseId | string | "44444444-4444-4444-8444-444444444444" | Identifier of the associated lease. |
| data.client_secret | string | "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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| operationId | string | "33333333-3333-4333-8333-333333333333" | Identifier of the associated operation. |
| deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| cardId | integer | 300 | Identifier of the associated card. |
| amount | integer | 2000 | The amount value. |
| transient | boolean | false | The transient value. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.operationId | string | "33333333-3333-4333-8333-333333333333" | Identifier of the associated operation. |
| data.status | string | "requires_action" | HTTP status code returned by the API. |
| data.paymentIntentId | string | "pi_example" | Identifier of the associated payment intent. |
| data.clientSecret | string | "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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.operationId | string | "33333333-3333-4333-8333-333333333333" | Identifier of the associated operation. |
| data.status | string | "succeeded" | HTTP status code returned by the API. |
| data.paymentIntentId | string | "pi_example" | Identifier of the associated payment intent. |
| data.transactionId | integer | 400 | Identifier 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
{}Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.operationId | string | "33333333-3333-4333-8333-333333333333" | Identifier of the associated operation. |
| data.status | string | "succeeded" | HTTP status code returned by the API. |
| data.transactionId | integer | 400 | Identifier 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| restaurantId | integer | 200 | Identifier of the restaurant. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.url | string | "https://ordering.example.test/?autoLoginJWT=REDACTED&embedded=true" | The url value. |
| data.expiresAt | string | "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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| restaurantId | integer | 200 | Identifier of the restaurant. |
| path | string | "/payment" | The path value. |
| cart | object | {…} | Object containing cart fields. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.url | string | "https://ordering.example.test/payment?autoLoginJWT=REDACTED&guestHandoff=RED… | The url value. |
| data.expiresAt | string | "2026-09-07T10:01:00.000Z" | Date or timestamp for expires. |
| data.cartExpiresAt | string | "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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.cart | object | {…} | 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| deploymentId | string | "11111111-1111-4111-8111-111111111111" | Identifier of the associated deployment. |
| restaurantId | integer | 200 | Identifier of the restaurant. |
Response
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.url | string | "https://atlas.example.test/embed/guest-footfall?locale=fr#token=REDACTED" | The url value. |
| data.expiresAt | string | "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.
// 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
{
"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.
| Property | Type | Example | Description |
|---|---|---|---|
| status | integer | 200 | HTTP status code returned by the API. |
| code | string | "guest_app_succeed" | Machine-readable application code for the result. |
| message | string | "Guest App request succeeded" | Human-readable result message. Do not use this value for program logic. |
| data | object | {…} | Endpoint-specific response payload. |
| data.url | string | "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.