Add-ons (v1.3)

v1.3

Add-Ons are optional - and occasionally mandatory (i.e., Disneyland Resorts®) - items that can be purchased alongside a base booking. They are configured by the Supplier at the Product, Rate, or traveler type level and range from consumables and equipment to experiences and upgrades.

Examples of real-world Add-ons: fast-lane pass, drink vouchers, meal packages, souvenir photographs, private guide upgrades, and mandatory equipment rentals.

🔗 Find the full details here: Reseller API Reference

Info

v1.2 partners: Add-ons is a v1.3 only capability. Your existing integration is unaffected. You can adopt it at any time by switching to the /v1.3/ base path.

How to Request Add-ons

Add-ons are not returned by default. Pass addOns=true on any of the following endpoints to include them:

Note

To also receive localized Add-on descriptions and media, combine both parameters: addOns=true & content=true.

Add-On Levels

Suppliers configure Add-ons at three levels of the Product hierarchy. The level controls which bookings the Add-on is available for and where addOns[] appears in the response.

Level

Where addOns[] appears in the response

Available for

Product

product.addOns[]

All Rates and all traveler types under this Product

Rate

rate.addOns[]

All traveler types under this specific Rate only

TravelerType

bookingTraveler.addOns[]

This specific traveler type (age band) only

In the Price Schedule and Availability responses, Add-ons from all three levels are embedded together inside each slot. Use the rateId and unitId fields to identify which level each Add-on belongs to:

rateId present

unitId present

Level

No

No

Product-level

Yes

No

Rate-level

Yes

Yes

TravelerType (unit) level

Understanding Data Presence (Read vs. Write)

As a channel manager, Travel Curious sits between the Supplier's ticketing system and your Reseller application. Because of this, it is important to distinguish between the data you receive from Suppliers versus the data you must send to us.

The indicators below describe what the Supplier is obligated to provide to Travel Curious. This helps you understand how reliable a field will be when you receive it in a GET response.

(Note: When you are sending data to us, such as in a POST /holds or POST /bookings request, you must still strictly follow the required fields defined in the API Reference schema).

General Field Presence Legend

What to expect in responses - This standard applies across all API capabilities

  • 🟢 Always: Required from the Supplier. Your integration can safely assume this field will be present in the response and never be null. Note for Optional Capabilities: If the field belongs to an optional feature (like Add-ons, Booking Questions or Content), this guarantee applies only if that feature is present in the response.

  • 🟠 Conditional: Required from the Supplier, but only under specific circumstances. Its presence depends on context, such as the hierarchy level or a dependency on another field.

  • 🟡 Occasional: Completely optional for the Supplier to provide. Your integration must handle nulls or missing keys gracefully.

💡 Developer Note for Add-ons: Because the existence of an Add-on is entirely dependent on whether a Supplier offers them, no fields within the Add-on schemas are universally guaranteed. You must always check if the addOns[] array exists before parsing its contents. The indicators below apply only after confirming an Add-on is present.

Add-ons Object Fields

Field

Type

Description

Presence

id

string

Unique identifier for this Add-on.

🟢 Always

internalName

string

Add-on internal name for back-office use

🟢 Always

title

string

Add-on customer-facing display name

🟡 Occasional

description

string

Details about the Add-on for display in your UI

🟡 Occasional

reference

string

Add-on internal code used by the Supplier for operational reference

🟡 Occasional

scope

AddOnScope

How the Add-on applies at booking time (see Scope section below)

🟢 Always

constraints[]

Constraint[]

Add-on purchase limits and eligibility restrictions

🟡 Occasional

deliveryFormats[]

DeliveryFormat[]

The Add-on ticket code formats (e.g., QR_CODE, PDF_417). The ticket code formats for Add-ons are independent of the main product deliveryFormats defined here. Suppliers have the flexibility to utilize separate and independent ticket code formats specifically for Add-ons.

🟡 Occasional

deliveryMethods[]

DeliveryMethod[]

The Add-on delivery method. TICKET (one per guest) or VOUCHER (one per booking).

🟡 Occasional

redemptionMethod

RedemptionMethod

How the guest redeems the Add-on at the venue. A value of MANIFEST indicates that the customer MUST provide a form of identification to redeem and as such a printed or digital copy of the ticket is OPTIONAL. A value of DIGITAL indicates that the customer MUST provide a copy of the ticket but MAY be digital or printed. A value of PRINT indicates that the customer MUST provide a printed copy of the ticket (this is typically only used when the Supplier must retain the printed copy for their records).

🟡 Occasional

policy

Policy

Add-on policy information related to holds and bookings

🟡 Occasional

content

Content

Localized descriptions, media, and features - same Content object used across the API

🟡 Occasional

extensions

Extensions

Key-value map for additional partner-specific data

🟡 Occasional

Scope

scope defines the unit at which the Add-on is purchased. This determines how many times it must be selected and how quantity is calculated.

Scope

Applies to

UI pattern

Example

product

Once per product in the booking

Single selection, applies automatically

Family photo taken once at the venue

booking

Once per booking, regardless of guest count

Toggle or quantity counter

Private guide upgrade, group drink vouchers

guest

Once per traveler in the booking

Guest-level selection, or auto-applied per guest

Bottle of water, mandatory equipment rental

day

Once per day of the experience

Date-aware quantity

Parking pass

Constraints and Purchase Limits

Suppliers can define specific restrictions on how many Add-ons can be purchased (e.g., requiring exactly one harness per guest, or capping drink vouchers at 10 per booking). When these restrictions apply, they are returned in the constraints[] array on the Add-on object.

🔗 See the full Traveler Type Constraints documentation for a complete list of constraint types, JSON payload examples, and UI implementation guidance on how to enforce min and max limits in your application.

Availability with Add-ons

When addOns=true is included in an availability request, each availability slot in the response carries an embedded addOns[] array. Each entry is an AddOnAvailability object.

AddOnAvailability Schema

Field

Type

Description

Presence

id

string

Unique ID for this Add-on avail slot.

🟢 Always

addOnId

string

The ID of the Add-on this availability entry refers to

🟢 Always

rateId

string

Present if this is a rate-level Add-on

🟡 Occasional - Present only if this is a Rate-level Add-on

unitId

string

Present if this is a traveler/unit level Add-on

🟡 Occasional - Present only if this is a Traveler Type (unit) level Add-on

localDateTimeStart

ISO 8601

The start time for the Add-on

🟢 Always

localDateTimeEnd

ISO 8601

The end time for the Add-on

🟢 Always

status

AvailabilityStatus

AVAILABLE, FREESALE, SOLD_OUT, or UNAVAILABLE

🟢 Always

vacancies

integer

The current number of Add-ons available

🟡 Occasional

maxCapacity

integer

Maximum configured capacity for this Add-on

🟡 Occasional

description

string

Add-on description

🟡 Occasional

extensions

Extensions

Key-value map for additional partner-specific data

🟡 Occasional

{ "availability": { "id": "f52ab94d-bcce-43bc-81ef-735c370d2cec", "start": "2026-06-20T17:00:00+02:00", "end": "2026-06-20T18:30:00+02:00", "capacity": 6, "maxCapacity": 10, "addOns": [ { "addOnId": "addon-photo-pkg", "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "status": "AVAILABLE", "vacancies": 20, "maxCapacity": 50 }, { "addOnId": "addon-llmp", "rateId": "0666f27f-2f16-4eba-91b7-28f08ce095d2", "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "status": "AVAILABLE", "vacancies": 10, "maxCapacity": 10 }, { "addOnId": "addon-harness", "rateId": "0666f27f-2f16-4eba-91b7-28f08ce095d2", "unitId": "cc7eea8c-6896-4ff3-82ff-a4399aea9264", "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "status": "FREESALE" } ] }, }
  • The first Add-on (addon-photo-pkg) has no rateId or unitId → it is a product-level Add-on.

  • The second (addon-llmp) has a rateId → it is a rate-level Add-on.

  • The third (addon-harness) has both rateId and unitId→ it is a traveler type level Add-on.

Price Schedule with Add-Ons

When addOns=true is included in a Price Schedule request, each PriceScheduleItem carries an embedded addOns[] array of AddOnPrice objects.

Info

Add-ons are not gap-filled. Unlike standard Rate prices, Add-on prices must always come from the Price Schedule, they will not fall back to static Rate-level data if no scheduled price exists.

AddOnPrice Schema

💡 Pricing Amounts Note: While the net, retail, and original price fields are individually marked as 🟠 Conditional, the Supplier is required to provide at least one of these three fields. Your integration should check which price format was provided and fall back accordingly.

Field

Type

Description

Presence

id

string

Unique identifier for the add-on price.

🟢 Always

addOnId

string

The ID of the parent AddOn this price belongs to.

🟢 Always

rateId

string

Present if this is a rate-level Add-on price

🟡 Occasional - Present only if this is a Rate-level Add-on

unitId

string

Present if this is a traveler type (unit) level Add-on price

🟡 Occasional - Present only if this is a Traveler Type (unit) level Add-on

localDateTimeStart

ISO 8601

The start time for the Add-on price

🟢 Always

localDateTimeEnd

ISO 8601

The end time for the Add-on price

🟢 Always

net

Price

The net price for this Add-on. The wholesale amount owed to the Supplier by the Reseller

🟠 Conditional - Present only if provided by Supplier. (See Pricing Amount note above).

retail

Price

The retail price for the Add-on. The recommended selling price for the guest, inclusive of any applied discounts. This is the final price the guest pays

🟠 Conditional - Present only if provided by Supplier. (See Pricing Amount note above).

original

Price

The original price for the Add-on. The original price before any discounts are applied. Used to display a strike-through price in the UI

🟠 Conditional - Present only if provided by Supplier. (See Pricing Amount note above).

includedTaxes[]

IncludedTaxes[]

Taxes and Fees already embedded in the Add-on price

🟠 Conditional - Present only if provided by Supplier. (See Pricing Amount note above).

description

string

Add-on description

🟡 Occasional

extensions

Extensions

Key-value map for additional partner-specific data

🟡 Occasional

{ "0666f27f-2f16-4eba-91b7-28f08ce095d2": { "2026-06-20": [ { "id": "aefe688e-4b90-4e92-b6b6-23a660eaa3f8", "name": "Adult", "net": { "amount": 3800, "currency": "EUR" }, "retail": { "amount": 4800, "currency": "EUR" }, "startTimes": [ "2026-06-20T17:00:00+02:00", "2026-06-20T19:00:00+02:00" ], "status": "ACTIVE", "addOns": [ { "addOnId": "addon-photo-pkg", "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "net": { "amount": 800, "currency": "EUR" }, "retail": { "amount": 1200, "currency": "EUR" } }, { "addOnId": "addon-llmp", "rateId": "0666f27f-2f16-4eba-91b7-28f08ce095d2", "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "net": { "amount": 1500, "currency": "EUR" }, "retail": { "amount": 2000, "currency": "EUR" } }, { "addOnId": "addon-harness", "rateId": "0666f27f-2f16-4eba-91b7-28f08ce095d2", "unitId": "cc7eea8c-6896-4ff3-82ff-a4399aea9264", "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "net": { "amount": 1000, "currency": "EUR" }, "retail": { "amount": 1000, "currency": "EUR" } } ] } ] } }

Submitting Add-Ons in a Hold

Add-Ons are submitted as an addOns[] array at the same level as the Hold items[] array. The same AddOnItem structure is used for both Hold and Booking.

💡 Submission Rule: Price fields within the AddOnItem object (net, retail, original) must be populated by the Reseller, representing the actual price at which the Add-on was sold. Travel Curious will not automatically inject or calculate this data during the Hold or Booking stages. You must always source the correct price format from the Price Schedule response prior to submitting the payload.

AddOnItem Schema (Holds & Bookings)

Field

Type

Description

Presence

id

string

Unique identifier for the add-on item.

🟢 Always

addOnId

string

The ID of the Add-on being purchased

🟢 Always

quantity

integer

Number of Add-ons being purchased

🟢 Always

localDateTimeStart

ISO 8601

The start time slot for the Add-on

🟢 Always

localDateTimeEnd

ISO 8601

The end time slot for the Add-on

🟢 Always

supplierId

string

The parent Supplier ID

🟢 Always

productId

string

The parent Product ID

🟢 Always

rateId

string

Present if this is a rate-level Add-on price

🟡 Occasional - Present only if this is a Rate-level Add-on

unitId

string

Present if this is a traveler type (unit) level Add-on price

🟡 Occasional - Present only if this is a Traveler Type (unit) level Add-on

net

Price

The net price (wholesale amount) owed to the Supplier by the Reseller for this Add-on.

🟡 Occasional

IMPORTANT: Price fields within the AddOnItem object must be populated by the Reseller, representing the actual price at which the Add-on was sold. Travel Curious will not automatically inject or populate this data during the Hold or Booking stages. Reseller must always source the correct price from the Price Schedule response prior to submitting the payload.

retail

Price

The final retail price paid by the guest for the Add-on.

🟡 Occasional

IMPORTANT: Price fields within the AddOnItem object must be populated by the Reseller, representing the actual price at which the Add-on was sold. Travel Curious will not automatically inject or populate this data during the Hold or Booking stages. Reseller must always source the correct price from the Price Schedule response prior to submitting the payload.

original

Price

The original price for the Add-on. The original price before any discounts are applied. Used to display a strike-through price in the UI

🟡 Occasional

IMPORTANT: Price fields within the AddOnItem object must be populated by the Reseller, representing the actual price at which the Add-on was sold. Travel Curious will not automatically inject or populate this data during the Hold or Booking stages. Reseller must always source the correct price from the Price Schedule response prior to submitting the payload.

resellerReference

string

Your internal reference for this Add-on item

🟡 Occasional - recommended to be provided by the Reseller in the request

supplierReference

string

An optional reference ID provided by the Supplier for this add-on item.

🟡 Occasional - Only returned on the response

barcodes

Barcode[]

The associated barcodes for the Add-ons.

🟡 Occasional - Only returned on the booking response

extensions

Extensions

Key-value map for additional partner-specific data

🟡 Occasional

{ "hold": { "id": "f7393cf6-097d-443a-919a-ae3ca518e040", "partnerId": "your-partner-id", "items": [ { "at": "2026-06-20T17:00:00+02:00", "availabilityId": "f52ab94d-bcce-43bc-81ef-735c370d2cec", "priceId": "aefe688e-4b90-4e92-b6b6-23a660eaa3f8", "productId": "f42d88a1-8892-4c2a-b772-992a8e310022", "rateId": "0666f27f-2f16-4eba-91b7-28f08ce095d2", "supplierId": "08e4f505-3374-447e-b3b0-32698603b155", "travelerType": "ADULT", "quantity": 2 } ], "addOns": [ { "addOnId": "addon-photo-pkg", "quantity": 1, "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "supplierId": "08e4f505-3374-447e-b3b0-32698603b155", "productId": "f42d88a1-8892-4c2a-b772-992a8e310022", "retail": { "amount": 1200, "currency": "EUR" }, "net": { "amount": 800, "currency": "EUR" } }, { "addOnId": "addon-llmp", "quantity": 2, "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "supplierId": "08e4f505-3374-447e-b3b0-32698603b155", "productId": "f42d88a1-8892-4c2a-b772-992a8e310022", "rateId": "0666f27f-2f16-4eba-91b7-28f08ce095d2", "retail": { "amount": 2000, "currency": "EUR" }, "net": { "amount": 1500, "currency": "EUR" } } ] } }

The Hold response echoes back the addOns[] array exactly as submitted.

Submitting Add-Ons in a Booking

The Booking request uses the same AddOnItem structure and the same addOns[] array at the same level as items[].

{ "booking": { "holdId": "f7393cf6-097d-443a-919a-ae3ca518e040", "resellerBookingRef": "RES-2026-00231", "customer": { "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+1 212 555 0100", "country": "USA" }, "items": [ ... ], "addOns": [ { "addOnId": "addon-photo-pkg", "quantity": 1, "localDateTimeStart": "2026-06-20T17:00:00+02:00", "localDateTimeEnd": "2026-06-20T18:30:00+02:00", "supplierId": "08e4f505-3374-447e-b3b0-32698603b155", "productId": "f42d88a1-8892-4c2a-b772-992a8e310022", "retail": { "amount": 1200, "currency": "EUR" }, "net": { "amount": 800, "currency": "EUR" } } ] } }

The Booking response echoes back addOns[] and may include barcodes[] on each AddOnItem when the Supplier provides them at booking confirmation.

Barcode and Ticket Delivery

Add-ons can either share a single ticket with the base booking or require their own separate tickets. Because Add-ons have their own deliveryMethods array (independent of the main product), you must check the deliveryMethods array on the AddOn object to understand how barcodes will be issued.

Delivery Method

How it works

When to use

VOUCHER

A single barcode covers the entire booking, including all base booking items and all Add-on items

When the Supplier issues one shared redemption code for everything

TICKET

Individual barcodes are issued for every base booking item and every individual Add-on item.

When each specific item and add-on requires its own scan at the venue

Note

Barcodes at intermediate or mixed levels are not supported. Add-on barcodes must be either at the top booking level (VOUCHER) or at the individual item level (TICKET). Check the deliveryMethods array on the specific AddOn object to determine which model applies to that Add-on, and ensure your ticket rendering logic handles both cases.

Pro-Tips & Edge Cases

  • Handling FREESALE Capacity: Add-on availability slots will return a status of either AVAILABLE or FREESALE. If an Add-on is FREESALE (meaning it has unlimited capacity), the vacancies and maxCapacity fields may be entirely omitted from the response. Ensure your parsers and UI logic do not break if these integers are missing.

  • Submitting Taxes: When submitting the net, retail, and original prices in your Hold or Booking request, check if the Price Schedule included an includedTaxes[] array for that Add-on. If it did, you should echo that array back in your AddOnItem payload to ensure accurate financial reporting.

  • Add-Ons and Cancellations: Add-Ons cannot currently be cancelled independently of their base item. Cancelling a Booking cancels any associated Add-Ons along with it - there is no partial/add-on-only cancellation at this time.

Use Cases

Booking-Scoped, Single Unit

Scenario: A boat tour is $50 per person. A group of 5 people wants a private guide. The guide costs a flat $100 for the whole group, and you only have 1 guide available per departure.

  • Level: Rate (The specific time slot)

  • Scope: booking (Applies once to the whole reservation)

  • Constraint (PurchaseLimit):

    • Min: 0 (Optional)

    • Max: 1 (You can't hire two guides for one boat)

  • Business Logic: Even though there are 5 guests, the API calculates: (5 x $50) + (1 x $100) = $350. The UI shows a toggle switch.

Quantity Counter - Booking-Scoped, Flexible Quantity

Scenario: At a live music event, guests can pre-purchase "Drink Vouchers" at a discount. A group of 4 people wants to buy 10 vouchers to share.

  • Level: Product (Available for all show times)

  • Scope: booking (Not tied to a specific person's ticket)

  • Constraint (PurchaseLimit):

    • Min: 1

    • Max: 20 (Limit per group to prevent bulk resale)

  • Business Logic: The user sees a quantity counter (+/-). They select "10". The API validates that 10 is less than 20 and calculates the price for the 10 vouchers regardless of the 4-person party size.

Auto-Applied, Non-Removable - Guest-Scoped, Mandatory

  • Scenario: A zip-lining tour requires a "Full Body Harness." For insurance reasons, every single participant must rent one, and the price is $10 per person.

    • Level: Traveler Type / Unit (Applied to every "Guest")

    • Scope: guest (Must be assigned to a specific person)

    • Constraint (PurchaseLimit):

      • Min: 1 (Required)

      • Max: 1 (One person cannot wear two harnesses)

    • Business Logic: The UI automatically selects this for every guest and disables any "Remove" button. If 5 guests are booked, the API calculates $5 x $10 = $50.