Skip to main content

Integration Flow

The typical end-to-end booking flow follows these steps:

Create Booking Session
│
▼
Discover Content (cities, groups, categories)
│
▼
Search Attractions (by city, date, type)
│
▼
Get Attraction Details (instances, pricing)
│
├── Theater: Get instances → price bands → seats (optional)
│
└── Tour: Get instances → check availability → select option
│
▼
Reserve Tickets
│
▼
Add Attendees (if mandatory_attendee = true)
│
▼
Review Summary
│
▼
Confirm & Pay (Stripe)
│
▼
Poll Transaction Status

Step 1: Create a Booking Session​

A booking session is required before searching for attractions or making reservations. It guarantees pricing for its duration.

POST /v2/booking-session

{
"distributor_session_id": "your-internal-session-id", // optional
"user_id": "your-internal-user-id", // optional
"language": "en" // optional
}

Response:

{
"booking-session": "6f299e0ce1ef66e612ac45552cf9fabc"
}

Important:

  • The session expires after 20 minutes of inactivity.
  • Any API call that includes the Booking-Session header refreshes the TTL. The expiration time is returned in the Booking-Session-Expires response header.
  • A session is invalidated after a successful transaction confirmation.
  • After expiry, reservation endpoints return 401 Unauthorized.

You can update session data (e.g., assign a user) later:

PUT /v2/booking-session
Headers: Booking-Session, Coras-Distributor
Body: { "user_id": "...", "language": "fr" }

Step 2: Discover Available Content​

Use the /v2/common endpoint to discover available cities, countries, groups, categories, and airports:

GET /v2/common?language_code=en
Headers: Coras-Distributor

Response includes:

  • cities — cities with available events or attractions (with search_key for use in attractions search)
  • countries — countries with city references
  • groups — attraction groups (e.g., "Theatre", "Sport Events")
  • categories / subcategories — for filtering attractions
  • airports — for travel-related lookups
  • continents — continent codes

Use the search_key from cities and group_id from groups in your attractions search queries.

Step 3: Search Attractions​

Search for attractions by city, date range, and other filters:

GET /v2/attractions?city=London&date_start=2025-07-24&date_end=2025-07-30&size=10&page=0
Headers: Booking-Session, Coras-Distributor

Key query parameters:

ParameterRequiredDescription
date_startYesStart date (YYYY-MM-DD)
date_endNoEnd date (YYYY-MM-DD)
cityNoCity name (use search_key from /v2/common)
attraction_type_idNo1 for theater, 2 for tour
group_idNoFilter by group
sizeNoResults per page (default: 10, max: 1000)
pageNoPage number (default: 0)
search_termNoFree-text search in name/description
currencyNoISO 4217 currency code
latitude / longitude / radiusNoGeo-search
tag_idsNoComma-separated tag IDs
categoriesNoComma-separated subcategory IDs

Response:

{
"alternative_results": false,
"results": [
{
"attraction_id": "54d07c194cc410e6325d9535d86c036d",
"attraction_name": "THRILLER LIVE",
"attraction_type_id": 1,
"attraction_details_url": "/attractions/54d07c194cc410e6325d9535d86c036d",
"city": "London",
"min_price": { "GBP": 2365, "EUR": 3775, ... },
"max_price": { "GBP": 2365, "EUR": 3775, ... },
"upcoming_instances": ["2025-07-24T18:30:00+00:00", ...],
...
}
],
"total": 42,
"categories_count": { "1": 6, "2": 14 }
}

Note: Prices are in minor units (e.g., pence, cents). 2365 GBP = £23.65.

Alternative results: If no attractions match your criteria and alternative_results=true, the API will broaden the search automatically (removing search_term/group_id, then extending the date range by 4 weeks).

Step 4: Get Attraction Details​

Use the unified Get Attraction endpoint — it works for both theater and tour attractions:

GET /v2/attractions/{attraction_id}?date_start=2025-07-24&date_end=2025-07-30
Headers: Booking-Session, Coras-Distributor

The response includes attraction_type ("THEATER" or "TOUR") to distinguish the format.

The response returns:

  • Attraction details — name, description, images, location, timezone, tags, categories
  • Instances — specific dates/times with availability and pricing
  • Attendee requirements — flags like mandatory_attendee, attendee_first_name_required, etc.

Deprecated alternatives: The type-specific endpoints GET /v2/theater-events/{id} and GET /v2/tours/{id} still work but are deprecated. Use GET /v2/attractions/{id} for all new integrations. See Deprecated Endpoints.

See Theater-Specific Flows and Tour-Specific Flows for details on each type.

Step 5: Reserve Tickets​

Use the unified reservations endpoint for both theater and tour attractions:

POST /v2/reservations
Headers: Booking-Session, Coras-Distributor

Theater reservation example:

{
"attraction_id": "54d07c194cc410e6325d9535d86c036d",
"instance_id": "a7c9798a0fb33d5a5883a3dca5682741",
"currency": "GBP",
"language_code": "en",
"tickets": [{
"price_type_id": "601APNNMRMBJQQPBSCNQGQHGKKCRPSSGN",
"number_of_tickets": 2,
"booking_fee": 300,
"seats": []
}],
"to_basket": true
}

Tour reservation example:

{
"attraction_id": "08e36beb3acadc1e69823b4d67aea83d",
"date": "2025-01-01",
"currency": "EUR",
"selection_id": "ZitVdTZOTjBW...",
"tickets": [{
"price_type_id": "6ca2ffe69a88f2bd02f6a35203b424fa",
"number_of_tickets": 2,
"booking_fee": 0
}],
"to_basket": true,
"tour_language": "en/SERVICE_WRITTEN",
"pickup_id": "notListed",
"pickup_entry": "Hotel name and address"
}

Deprecated alternatives: The type-specific endpoints POST /v2/theater-events/reservations and POST /v2/tours/reservations still work but are deprecated. Use POST /v2/reservations for all new integrations. See Deprecated Endpoints.

Important:

  • booking_fee must fall within the min_booking_fee / max_booking_fee range from the pricing response.
  • to_basket defaults to true. Set to false if you want to add to basket manually later via PUT /v2/reservations/{reservation_id}.
  • Some attractions don't support multiple_purchases — check the multiple_purchases flag on the attraction. If false, separate reservations are needed per price band.
  • Attractions from different providers may not be purchasable in the same transaction. Watch for error code 36 ("Attractions cannot be purchased together").

Step 6: Handle Attendee Data​

If mandatory_attendee is true on the attraction, you must provide attendee details for each ticket before confirming the reservation.

POST /v2/attendees
Headers: Booking-Session
Body:
{
"attendees": [{
"ticket_id": "9363d8f6-2602-4703-aa5e-f3a02d1f7f61",
"first_name": "Jon",
"second_name": "Snow",
"id_number": "PASSPORT-123"
}]
}

Check the attendee_*_required flags on the attraction to determine which fields are mandatory (e.g., attendee_date_of_birth_required, attendee_email_required, etc.).

To update existing attendees:

PUT /v2/attendees
Headers: Booking-Session
Body: { "attendees": [...] }

Step 7: Review Reservation Summary​

Get a summary of all active reservations in the current booking session:

GET /v2/reservations
Headers: Booking-Session, Coras-Distributor

Response:

{
"reservations": [
{
"reservation_id": "7a7216e7509d4f1eba24135ef4757fb1",
"attraction_name": "THRILLER LIVE",
"attraction_type_id": 1,
"total_price": 6600,
"tickets": [...],
...
}
],
"total_price": 6600
}

Step 8: Confirm & Pay​

Coras supports two confirmation flows — choose the one that fits your integration:

AspectAsync (PUT /v2/reservations)Sync (POST /v2/reservations/confirm)
Response202 Accepted with client_secret200 OK — payment already complete
payment_method_idOptional (can use token)Required
AttendeesCan include in request bodyMust pre-register via POST /v2/attendees
Webhook required✅ Yes❌ No
3DS/SCA support✅ Client-side via Stripe.js❌ Server-side only
Polling needed✅ Yes❌ No
Best forWeb checkout with end-user presentServer-to-server / API integrations where the distribution partner is merchant of record (MoR)

Option A: Async Flow (PUT /v2/reservations)​

Best for web checkout flows where the end-user is present and 3DS/SCA authentication is needed.

PUT /v2/reservations
Headers: Booking-Session, Coras-Distributor
{
"email": "customer@example.com",
"first_name": "Jon",
"second_name": "Snow",
"phone_number": "+44-111-222-333",
"reservations": [
"7a7216e7509d4f1eba24135ef4757fb1"
]
}

Response (202 Accepted):

{
"transaction_id": "123406179e47a6a8f06f6c486abcde",
"user_id": "6123c32027abcd2ac07c4c5a887abcd",
"client_secret": "pi_1FAB04K10yRCtOElcyFjdy6r_secret_0RvQ10CfZC3FnJJo047OJIDMH"
}

After receiving the response:

  1. Use the client_secret with Stripe.js to complete 3DS/SCA authentication:

    const { error, paymentIntent } = await stripe.handleCardPayment(
    clientSecret,
    cardElement
    );
  2. Stripe sends a webhook to Coras (payment_intent.amount_capturable_updated), which triggers the backend confirmation pipeline (seat capture, ticket generation, etc.).

  3. Poll GET /v2/users/{user_id}/transactions for the final transaction status (see Step 9).

See Payment Integration for full Stripe details.

Option B: Sync Flow (POST /v2/reservations/confirm)​

Best for server-to-server integrations with pre-authorized or saved payment methods. No webhook or polling needed.

Prerequisites:

  • Attendees must be registered beforehand via POST /v2/attendees (if required by the attraction).
  • A Stripe payment_method_id is required (not a token).
POST /v2/reservations/confirm
Headers: Booking-Session, Coras-Distributor
{
"email": "customer@example.com",
"first_name": "Jon",
"second_name": "Snow",
"phone_number": "+44-111-222-333",
"payment_method_id": "pm_1FAB04K10yRCtOElcyFjdy6r",
"reservations": [
"7a7216e7509d4f1eba24135ef4757fb1"
]
}

Response (200 OK):

{
"transaction_id": "123406179e47a6a8f06f6c486abcde",
"user_id": "6123c32027abcd2ac07c4c5a887abcd",
"loyalty_programme": null
}

No client_secret is returned — payment is already confirmed. The Stripe webhook ignores synchronous transactions. The response itself confirms success or failure.

Phone Number Verification (Both Flows)​

If any reservation requires phone number verification (phone_number_verification_required = true), first call:

POST /v2/phone-number-verification
Headers: Booking-Session, Coras-Distributor
Body: { "phone_number": "+44-111-222-333" }

Then call:

POST /v2/verification-code-confirmation
Headers: Booking-Session, Coras-Distributor
Body: { "phone_number": "+44-111-222-333", "phone_number_verification_code": "123456" }

to confirm the code sent to the user's phone. This must be done before confirming reservations.

Step 9: Poll Transaction Status (Async Flow Only)​

Note: This step only applies to the async flow (PUT /v2/reservations). For the sync flow, the response already contains the final result.

Poll for the transaction status:

GET /v2/users/{user_id}/transactions
Headers: Coras-Distributor

Transaction statuses:

StatusDescription
ADDEDTransaction created, awaiting payment
PENDINGPayment is being processed
PAIDSuccessfully completed
FAILEDPayment failed
SUSPENDEDCard issue — user can retry with new card details
REFUNDEDFully refunded
PARTIAL_REFUNDEDPartially refunded

Step 10: Cancel Reservations (Optional)​

Cancellation is only possible before a booking has been confirmed.

Cancel a single reservation:

DELETE /v2/reservations/{reservation_id}
Headers: Booking-Session, Coras-Distributor

Cancel all reservations in the session:

DELETE /v2/reservations
Headers: Booking-Session, Coras-Distributor

Both return HTTP 204 on success.