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-Sessionheader refreshes the TTL. The expiration time is returned in theBooking-Session-Expiresresponse 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 (withsearch_keyfor use in attractions search)countries— countries with city referencesgroups— attraction groups (e.g., "Theatre", "Sport Events")categories/subcategories— for filtering attractionsairports— for travel-related lookupscontinents— 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:
| Parameter | Required | Description |
|---|---|---|
date_start | Yes | Start date (YYYY-MM-DD) |
date_end | No | End date (YYYY-MM-DD) |
city | No | City name (use search_key from /v2/common) |
attraction_type_id | No | 1 for theater, 2 for tour |
group_id | No | Filter by group |
size | No | Results per page (default: 10, max: 1000) |
page | No | Page number (default: 0) |
search_term | No | Free-text search in name/description |
currency | No | ISO 4217 currency code |
latitude / longitude / radius | No | Geo-search |
tag_ids | No | Comma-separated tag IDs |
categories | No | Comma-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}andGET /v2/tours/{id}still work but are deprecated. UseGET /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/reservationsandPOST /v2/tours/reservationsstill work but are deprecated. UsePOST /v2/reservationsfor all new integrations. See Deprecated Endpoints.
Important:
booking_feemust fall within themin_booking_fee/max_booking_feerange from the pricing response.to_basketdefaults totrue. Set tofalseif you want to add to basket manually later viaPUT /v2/reservations/{reservation_id}.- Some attractions don't support
multiple_purchases— check themultiple_purchasesflag on the attraction. Iffalse, 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:
| Aspect | Async (PUT /v2/reservations) | Sync (POST /v2/reservations/confirm) |
|---|---|---|
| Response | 202 Accepted with client_secret | 200 OK — payment already complete |
payment_method_id | Optional (can use token) | Required |
| Attendees | Can include in request body | Must 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 for | Web checkout with end-user present | Server-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:
-
Use the
client_secretwith Stripe.js to complete 3DS/SCA authentication:const { error, paymentIntent } = await stripe.handleCardPayment(clientSecret,cardElement); -
Stripe sends a webhook to Coras (
payment_intent.amount_capturable_updated), which triggers the backend confirmation pipeline (seat capture, ticket generation, etc.). -
Poll
GET /v2/users/{user_id}/transactionsfor 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_idis 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:
| Status | Description |
|---|---|
ADDED | Transaction created, awaiting payment |
PENDING | Payment is being processed |
PAID | Successfully completed |
FAILED | Payment failed |
SUSPENDED | Card issue — user can retry with new card details |
REFUNDED | Fully refunded |
PARTIAL_REFUNDED | Partially 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.