Booking API

List and cancel a contact's bookings with the customer-facing OmniLab Booking endpoints.

The Booking API lets your backend read and manage the bookings a contact has made on an OmniLab campaign — for example to power an account page that shows a customer's upcoming bookings and lets them cancel.

All endpoints are server-to-server: request an access token on your own backend, then call OmniLab with a bearer token. Never expose the client secret in a browser, kiosk page, or mobile app bundle.

Credentials come from your Customer Success Manager

The client_id, client_secret, audience, and the exact API host for each environment are not self-service. Request them from your OmniLab Customer Success Manager and confirm which endpoints are enabled for your tenant.

Base URLs and environments

Every call uses the /v1/ base path on a tenant-specific host. The host follows this pattern, where <tenant> is your tenant key and <env> is the environment:

EnvironmentAPI host pattern
Developmenthttps://<tenant>.api.omnilab-dev.21-digital.com
Staging (UAT)https://<tenant>.api.omnilab-uat.21-digital.com
Productionhttps://<tenant>.api.omnilab-prod.21-digital.com

Keep staging and production credentials separate, and build and test against staging first. See Base URLs and environments for the wider rules.

Authenticate

Get a token with the client-credentials flow, then send it as Authorization: Bearer <token> on every Booking API call.

Request an access token
curl -X POST "https://<api-host>/v1/oauth:token" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "audience": "YOUR_AUDIENCE",
    "grant_type": "client_credentials"
  }'
Token response
{
  "access_token": "YOUR_ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600
}

Treat expires_in as the token lifetime in seconds: cache the token on your backend and refresh it before it expires, or after a 401 response. The full token guidance lives in Authentication.

Key concepts

Before the endpoint reference, three ideas explain how identifiers fit together.

How a contact is identified

IdentifierWhat it isWho uses it
External ID (external_id)The ID your own system already uses for this customer. You pass it to OmniLab when fetching bookings.You — this is your handle for the contact.
Contact ID (contacts/<uuid>)OmniLab's internal identifier for the same person. OmniLab resolves it from your external ID for you.OmniLab internally. You normally never send it.
Public key (public_key)The public-facing key of a campaign (interaction). It appears in experience URLs and lets you scope a fetch to a single campaign.Identifies the campaign, not the person.

In short: the external ID identifies the person, and the public key identifies the campaign. You only ever need to store the external ID you already have.

Campaigns, touchpoints, and the activity ID

A campaign in OmniLab is an interaction, identified by its public_key. An interaction can contain one or more bookable activities — also called touchpoints. Each booking in the response carries an activity object with its own id. That activity.id is the touchpoint identifier, and it is the key to telling bookings apart when a campaign has more than one bookable activity.

activity.id is unique within a campaign, not across campaigns — the same id can appear under two different campaigns. To identify a touchpoint uniquely, use activity.id together with the campaign's interaction.public_key. The two use cases below show this in practice.

The booking identifier and statuses

Each booking is identified by the booking field — a unique identifier (a UUID). Pass that exact value back to cancel the booking; don't add a prefix or transform it.

Booking statusMeaning
CONFIRMEDThe booking is confirmed.
WAITLISTThe contact is on the waitlist for a full slot.
CANCELLEDThe booking was cancelled.

Endpoints

List a contact's bookings

GET /v1/interactions:fetchContactBookings

Returns every booking for one contact, with pagination and optional filters.

Query parameters

ParameterRequiredDescription
external_idYesThe contact's external ID in your system.
public_keysNoScope to a single campaign by its public key. Only one value is supported. Cannot be combined with interactions.
interactionsNoScope to a single campaign by its resource name (interactions/<uuid>). Only one value is supported. Cannot be combined with public_keys.
slot_date_range.fromNoLower bound for the slot start time, RFC 3339 (for example 2024-12-10T12:30:00Z).
slot_date_range.toNoUpper bound for the slot start time, RFC 3339.
booking_statusNoFilter by status. Repeat the parameter to combine values (CONFIRMED, WAITLIST, CANCELLED).
languageNoLanguage code used to translate campaign text in the response.
page_sizeNoResults per page. Default 50.
page_numberNoPage to return, starting at 1. Default 1.

Filter by one campaign at a time

You can narrow results to a single campaign with either public_keys or interactions, but not both, and only one value each. Omit both to return the contact's bookings across all campaigns.

Fetch confirmed bookings for one contact
curl -X GET "https://<api-host>/v1/interactions:fetchContactBookings?external_id=9b33d8c1-64bd-4eff-b0e4-d2fb29ecbe83&booking_status=CONFIRMED&page_size=50&page_number=1" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response

FieldTypeDescription
bookingsarrayThe contact's bookings for this page.
page_numbernumberThe page returned.
page_sizenumberResults per page.
total_countnumberTotal bookings matching the filters.

Each booking in bookings contains the fields below. See Field formats and conventions for the exact format of each.

FieldTypeDescription
activityobjectThe booked touchpoint, with id (the touchpoint ID), title (localized name), and location_hint (localized location text).
interactionobjectThe campaign, with public_key and timezone (IANA name, for example Europe/London).
groupobjectThe collection the campaign belongs to, with unique_key and display_name.
slot_starting_atstringSlot start time, RFC 3339 in UTC (ends in Z).
slot_duration_in_minutesintegerSlot length in minutes. Omitted when the slot has no set duration — see below.
booking_statusstringCONFIRMED, WAITLIST, or CANCELLED.
booked_atstringTime of the booking's current status (confirmation, waitlist, or cancellation), RFC 3339 in UTC.
ticket_typeobjectThe ticket booked, with display_name and has_limited_capacity.
hostobjectThe specific host the contact booked with, when the activity offers a choice of hosts (for example a named station or staff member), with display_name. The location is in activity.location_hint.
bookingstringThe booking's unique identifier (a UUID). Pass this exact value to the cancel endpoint.
Example response (one booking)
{
  "bookings": [
    {
      "activity": {
        "id": "b8b2a0e2-1f3c-4a7d-9c52-0c8f2d1a6e44",
        "title": "Santa Photo Experience",
        "location_hint": "Level 1, near the fountain"
      },
      "interaction": {
        "public_key": "winter-festival",
        "timezone": "Europe/London"
      },
      "group": {
        "unique_key": "example-brand",
        "display_name": "Example Brand"
      },
      "slot_starting_at": "2024-12-10T12:30:00Z",
      "slot_duration_in_minutes": 30,
      "booking_status": "CONFIRMED",
      "booked_at": "2024-12-01T09:15:00Z",
      "ticket_type": {
        "display_name": "Standard entry",
        "has_limited_capacity": true
      },
      "host": {
        "display_name": "Photo Station 2"
      },
      "booking": "2394d604-afcc-4f7f-98c1-ef3676c20d6b"
    }
  ],
  "page_number": 1,
  "page_size": 50,
  "total_count": 1
}

Field formats and conventions

FieldFormat and behavior
slot_starting_atRFC 3339 timestamp in UTC, ending in Z (for example 2024-12-10T12:30:00Z).
slot_duration_in_minutesInteger minutes, from the booked slot's configuration (the chosen host's slot length, or the activity's when there's no host choice). Omitted from the response when the slot has no set duration — never defaulted to 60, so type it as optional.
booked_atRFC 3339 timestamp in UTC. Holds the time of the current status: confirmation for CONFIRMED, waitlist for WAITLIST, cancellation for CANCELLED.
booking_statusOne of CONFIRMED, WAITLIST, or CANCELLED.
interaction.timezoneIANA timezone name (for example Europe/London), not an offset. Convert the UTC slot times into it to show the contact their local time.
activity.title, activity.location_hintLocalized plain strings in the campaign's resolved language. Edge case: when the chosen or default language has no value for that field, you may instead receive a raw language map serialized as a string, for example {"fr":"…"}. Parse defensively — if the value parses as a JSON object, take the entry for your language or the first available one.
language (query param)Base language code such as en or fr (region subtags like fr-CA are reduced to fr). Omit it, or pass one the campaign doesn't offer, and OmniLab returns the campaign's default language.

Pass the slot_date_range.from and slot_date_range.to filters as RFC 3339 in UTC as well.

Cancel a booking

POST /v1/interactions:cancelInteractionBooking

Cancels one booking and its tickets. Pass the booking value returned by the fetch call, exactly as received.

FieldRequiredDescription
bookingYesThe booking identifier, exactly as returned in the booking field of fetchContactBookings (a UUID, with no prefix).
Cancel a booking
curl -X POST "https://<api-host>/v1/interactions:cancelInteractionBooking" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "booking": "82ce03a0-939e-423f-811d-0345eacdc137"
  }'

A successful cancellation returns an empty JSON object ({}).

Use cases

Campaign with a single activity touchpoint

When a campaign has only one bookable activity, every booking in the response shares the same activity.id. You can list the bookings directly without grouping — each row is simply one slot the contact booked for that activity.

Campaign with multiple activity touchpoints

When a campaign offers several bookable activities (for example a Santa photo and a craft workshop in the same winter campaign), each booking carries the activity.id of the touchpoint it belongs to. Use activity.id to group or label bookings by activity, so the customer sees "Santa Photo Experience" and "Craft Workshop" as separate lines rather than a flat list. The activity.title gives you a ready-made label for each group.

Display bookings on an account page

A typical account-page flow:

Fetch the contact's confirmed bookings

On your backend, call fetchContactBookings with the customer's external_id and booking_status=CONFIRMED. Page through the results if total_count is larger than your page_size.

Group and render the list

Group bookings by campaign (interaction.public_key) and touchpoint (activity.id) — remember activity.id repeats across campaigns. Show each slot using slot_starting_at, slot_duration_in_minutes, ticket_type.display_name, and host.display_name, and display every time in interaction.timezone.

Offer "Cancel"

Ask the customer to confirm, call cancelInteractionBooking with the booking value, then refresh the list so the cancelled slot disappears.

Postman collections

OmniLab maintains a Postman collection per environment (Development, Staging/UAT, and Production) covering all of the endpoints above. Request the collection for your environment from your Customer Success Manager.

Never commit credentials

The collections ship with placeholder credentials only. Use the client_id, client_secret, and audience your Customer Success Manager provisions for you, store them in a secret manager, and keep them out of source control and shared links.

Good practices

  • Run the token exchange on your backend and call the Booking API server-to-server.
  • Cache the access token and refresh it before expires_in elapses or after a 401.
  • Send date filters (slot_date_range.from / slot_date_range.to) in UTC.
  • Display slot times in interaction.timezone, which is an IANA timezone name.
  • Confirm with the customer before cancelling, and refresh the list afterwards.
  • Handle an empty bookings array gracefully — a contact may have no bookings.

Was this helpful?

Optional comments help us improve this page for future authors and readers.

On this page