Sandbox mode — all API calls hit test environments. No real bookings or charges.

API Documentation

Full REST API for searching, checking availability, and booking tours and activities. All responses are JSON. No authentication is required for sandbox testing.

Base URL
http://localhost:3017
Format
JSON
Auth (Sandbox)
None required
Sandbox vs Production
Sandbox is open and free — test the full booking flow with no real charges. For production access with live provider inventory, contact us.

Booking Workflow

The typical integration follows this flow:

1
Search
Find cities & activities
2
Availability
Check dates & time slots
3
Hold & Pay
Reserve + Stripe checkout
4
Confirm
Finalize booking
GET/api/search/autocomplete

Low-latency typeahead. Returns a grouped payload — cities, pois, tags, activities — suitable for rendering a search-as-you-type dropdown. Designed to be called on every keystroke.

Parameters

NameTypeRequiredDescription
qstringYesSearch query (min 2 chars)
langstringNoLanguage code for localized names (default: en)
limitnumberNoCap per bucket (default: 5, max: 10)
providersstringNoComma-separated provider IDs (2=Tiqets, 3=Viator, 7=Headout). Scopes the activities bucket only — cities, pois, tags stay provider-agnostic.
SandboxTry it
GET/api/search/destinations

Search for cities and destinations by name.

Parameters

NameTypeRequiredDescription
qstringYesSearch query (min 2 chars)
langstringNoLanguage code (default: en)
limitnumberNoMax results (default: 10, max: 20)
SandboxTry it
GET/api/search/activities

Search activities by text, city, or filters. Returns paginated results with pricing.

Parameters

NameTypeRequiredDescription
qstringNoFull-text search query
citynumber|stringNoCity ID or slug/name (e.g. 84632 or rome). Unknown value returns empty.
city_namestringNoDeprecated alias for city when passing a name
sortstringNorank (default, recommended ranking), caliber, rating, reviews, price_asc, price_desc
pagenumberNoPage number (default: 1)
limitnumberNoResults per page (default: 20, max: 100)
langstringNoLanguage code (default: en). Localizes name, description, url when a translation exists.
currencystringNo3-letter currency code (default: EUR). Drives the price field.
providersstringNoComma-separated provider IDs (2=Tiqets, 3=Viator, 7=Headout)
min_ratingnumberNoMinimum review rating
min_pricenumberNoMinimum price in the selected currency's minor unit (cents for EUR/USD)
max_pricenumberNoMaximum price in the selected currency's minor unit
tagsstringNoComma-separated tag IDs or slugs — OR semantics (any match), e.g. 70787,food-tours. Legacy singular tag accepts one value. Unknown tokens are dropped; if none resolve the response is empty.
poisstringNoComma-separated POI IDs or slugs — OR semantics, e.g. colosseum,vatican-museums. Legacy singular poi accepts one value. Unknown tokens are dropped; if none resolve the response is empty.
featuresstringNoComma-separated characteristic keys — AND semantics, e.g. free_cancellation,mobile_ticket. Supported: free_cancellation, instant_confirmation, mobile_ticket, accessible. Unknown tokens are dropped.
tour_typesstringNoComma-separated tour-type keys — AND semantics, e.g. guided,private. Supported: guided, private, ticket, audioguide, night. Combines with features (also AND). Unknown tokens are dropped.
facetsbooleanNoReturn a facets object alongside the results (default: true). Pass false to skip and shave latency.

Facets

When facets=true (default), the response includes a facets object with refinement options. Each facet's counts are computed over the activities matching every other dimensional filter — so clicking a provider chip re-narrows the tag/price/rating/feature counts, but the provider counts themselves stay stable so the user can pivot. Bounds in facets.prices are in the currency's minor unit (cents for EUR/USD), matching the min_price / max_price params. Capped at the top 3,000 activities by caliber when the matching corpus is larger — facets.capped tells you when that happened. The facets.features (characteristics) and facets.tourTypes arrays correspond to the features and tour_types filter params — feed a chip's key straight back to filter.

{
  "facets": {
    "tags":      [{ "id": 70787, "name": "Walking Tours", "slug": "walking-tours", "type": "activity_type", "count": 487 }, ...],
    "providers": [{ "id": 3, "count": 320 }, { "id": 4, "count": 88 }, ...],
    "prices":    { "currency": "EUR", "min": 500, "max": 98000, "buckets": [{ "from": 0, "to": 2500, "count": 145 }, { "from": 2500, "to": 5000, "count": 87 }, ..., { "from": 10000, "to": null, "count": 23 }] },
    "ratings":   [{ "min": 3, "count": 1024 }, { "min": 3.5, "count": 870 }, { "min": 4, "count": 520 }, { "min": 4.5, "count": 230 }],
    "features":  [{ "key": "free_cancellation", "count": 234 }, { "key": "instant_confirmation", "count": 412 }, { "key": "mobile_ticket", "count": 540 }, { "key": "accessible", "count": 130 }],
    "tourTypes": [{ "key": "guided", "count": 410 }, { "key": "private", "count": 96 }, { "key": "ticket", "count": 220 }, { "key": "audioguide", "count": 24 }, { "key": "night", "count": 31 }],
    "corpusSize": 1234,
    "capped": false
  }
}
SandboxTry it
GET/api/search/tags

Search tags (activity categories and filters) by name. Optionally include the top-ranked activities for each tag, so you can build faceted browse UIs without hardcoding IDs.

Parameters

NameTypeRequiredDescription
qstringNoName or slug substring
typestringNoactivity_type or filter (default: both)
limitnumberNoTags to return (default: 20, max: 100)
withActivitiesbooleanNoWhen true, also returns top activities per tag
activitiesLimitnumberNoPer-tag activity cap when withActivities=true (default: 5, max: 20)
cityIdnumber|stringNoRestrict returned activities to this city, by ID or slug (e.g. 84632 or rome). Tags themselves are global. Alias: city. Unknown value returns empty.
langstringNoLanguage code for localized tag names (default: en)
SandboxTry it
GET/api/search/pois

Points of interest in a city — attractions, districts, landmarks. POIs are pulled from ts.pois by their own city_id, so neighbour-city POIs incorrectly linked via legacy parser data cannot leak in. Sorted by how many of the city's valid/enabled/available activities link to each POI (POIs with no activities still appear, sorted last).

Scoping note: counts (and embedded activities, if requested) are restricted to activities whose city_id equals the queried city. Day trips that visit a Florence POI but depart from Rome are excluded, matching what a user sees when browsing the city.

Parameters

NameTypeRequiredDescription
citynumber|stringYesCity ID or slug (e.g. 87000 or florence). Alias: cityId. Unknown value returns empty.
typestringNoattraction, district, or landmark (default: all)
categorystringNoCategory id or slug, CSV for multiple (e.g. museums,theme-parks). Each POI also returns its category object.
qstringNoName or slug substring filter
limitnumberNoPOIs to return (default: 20, max: 100)
withActivitiesbooleanNoWhen true, embeds top activities per POI (sorted by caliber)
activitiesLimitnumberNoPer-POI activity cap when withActivities=true (default: 5, max: 20)
langstringNoLanguage code for localized POI names (default: en)
SandboxTry it
GET/api/search/product/:id

Get full product detail including description, gallery, itinerary, pricing, features, and cancellation policy.

Parameters

NameTypeRequiredDescription
idnumberYesActivity ID (URL param)
langstringNoLanguage code (default: en)
SandboxTry it
GET/api/search/product/:id/reviews

Paginated reviews for a single activity.

Parameters

NameTypeRequiredDescription
idnumberYesActivity ID (URL param)
pagenumberNoPage number (default: 1)
limitnumberNoReviews per page (default: 10, max: 50)
sortstringNorecent (default), rating_desc, rating_asc
minRatingnumberNoMinimum star rating
langstringNoRestrict to reviews written in this language
SandboxTry it
POST/api/availability/schedule

Get a monthly availability calendar. Returns which dates have available slots.

Body Parameters

NameTypeRequiredDescription
activityIdnumberYesActivity ID
dateFromstringYesStart date (YYYY-MM-DD)
dateTostringYesEnd date (YYYY-MM-DD)
currencystringNoCurrency code (default: EUR)
SandboxTry it
POST/api/availability/slots

Get real-time bookable time slots for a specific date. Use after the user selects a date from the schedule.

Body Parameters

NameTypeRequiredDescription
activityIdnumberYesActivity ID
datestringYesDate (YYYY-MM-DD)
adultsnumberNoNumber of adults (default: 2)
childrennumberNoNumber of children (default: 0)
currencystringNoCurrency code (default: EUR)
SandboxTry it
POST/api/booking/options

Get booking form fields: product variants, age bands, booking questions, language guides, and cancellation policy.

Body Parameters

NameTypeRequiredDescription
activityIdnumberYesActivity ID
currencystringNoCurrency code (default: EUR)
productOptionCodestringNoSpecific variant code (returns all if omitted)
SandboxTry it
POST/api/booking/hold

Hold a booking and create a Stripe Checkout session. Returns a checkoutUrl to redirect the customer to Stripe for payment. The hold uses manual capture — the card is authorized but not charged until the booking is confirmed with the provider.

Body Parameters

NameTypeRequiredDescription
activityIdnumberYesActivity ID
datestringYesTravel date (YYYY-MM-DD)
startTimestringNoTime slot (HH:MM) or omit for ON_DEMAND
productOptionCodestringYesProduct variant code (from booking/options)
passengersobject|arrayYesEither { adults, children?, infants?, seniors?, youth? } or [{ bandId: 'adult'|'child'|..., count }]. adults >= 1 is required.
contactobjectYes{ firstName, lastName, email, phone }
travelersarrayNoTraveler details per person
bookingAnswersarrayNoAnswers to booking questions
languageGuideobjectNoSelected language guide
currencystringNoCurrency (default: EUR)
baseUrlstringNoOrigin to send the customer back to after Stripe (e.g. https://yourdomain.com). Authenticated/API callers only — the customer lands on {baseUrl}/booking/success or /booking/cancel. Defaults to the request's own origin.

Response

{
  "checkoutUrl": "https://checkout.stripe.com/...",
  "internalRef": "TS-3-abc123-def456",
  "expiresAt": "2026-04-10T15:30:00Z",
  "retailPrice": 5400,
  "currency": "EUR"
}
SandboxTry it
POST/api/booking/confirm

Confirm a booking after Stripe payment. Call this with the session_id from the Stripe success redirect. This captures the payment and confirms with the provider.

Body Parameters

NameTypeRequiredDescription
sessionIdstringYesStripe Checkout session ID (from success URL query param)

Response

{
  "status": "confirmed",
  "internalRef": "TS-3-abc123-def456",
  "providerRef": "BR-12345678",
  "voucherUrl": "https://...",
  "ticketUrl": "https://...",
  "activityName": "Colosseum Skip-the-Line Tour",
  "travelDate": "2026-05-15"
}
SandboxTry it
POST/api/booking/status

Check the current status of a booking.

Body Parameters

NameTypeRequiredDescription
internalRefstringYesInternal booking reference (e.g. TS-3-abc123-def456). ref is accepted as an alias.
SandboxTry it
POST/api/booking/cancel

Cancel a booking. Cancels with the provider and refunds the Stripe payment.

Body Parameters

NameTypeRequiredDescription
internalRefstringYesInternal booking reference. ref is accepted as an alias.
SandboxTry it
GET/api/data/destinations

Discover the ranking scopes available on the platform, resolved into human-friendly { city, poi } records. Use this to drive a "popular destinations" UI without hardcoding city IDs.

Parameters

NameTypeRequiredDescription
qstringNoFilter by city name/slug substring
limitnumberNoMax scopes to return (default: 50, max: 500)
cursornumberNoOpaque pagination cursor returned as nextCursor
SandboxTry it
GET/api/data/categories

List the visible tag categories. For free-text tag search use /api/search/tags instead.

Parameters

NameTypeRequiredDescription
typestringNoactivity_type or filter (default: both)
langstringNoLanguage code for localized names (default: en)
limitnumberNoMax categories (default: 200, max: 500)
SandboxTry it

Stripe Payment Flow

The booking system uses Stripe's manual capture mode for safe payment handling:

  1. HoldPOST /api/booking/hold creates a Stripe Checkout session with capture_method: manual. The customer's card is authorized but not charged.
  2. Redirect — Redirect the customer to the returned checkoutUrl. Stripe handles card input securely.
  3. Success callback — After payment, Stripe redirects to your success URL with ?session_id=....
  4. Confirm — Call POST /api/booking/confirm with the session ID. This confirms with the provider, then captures the payment.
  5. Failure — If the provider rejects the booking, the payment authorization is cancelled automatically (no charge).
30-minute expiry
Stripe sessions expire after 30 minutes. If the customer doesn't complete payment in time, the hold is released.

MCP Server

This API is available as a Model Context Protocol (MCP) server, allowing AI agents (Claude, GPT, etc.) to search, check availability, and book activities programmatically.

Configuration

Add the following to your MCP client config (e.g. claude_desktop_config.json or .claude/settings.json):

{
  "mcpServers": {
    "tours-api": {
      "command": "npx",
      "args": ["-y", "@anthropic-ai/mcp-remote", "http://localhost:3017/api/mcp"]
    }
  }
}

Available Tools

ToolDescription
search_citiesSearch for cities by name. Params: query, lang
search_activitiesSearch activities by city name or text query. Params: city_name, query, providers, sort, limit
get_productGet full product detail. Params: activity_id, lang
check_availabilityGet available dates and time slots. Params: activity_id, date_from, date_to
get_slotsGet bookable time slots for a date. Params: activity_id, date, adults
get_booking_optionsGet booking form fields (variants, questions). Params: activity_id
hold_bookingHold a booking and get Stripe checkout URL. Params: activity_id, date, start_time, product_option_code, adults, contact
confirm_bookingConfirm after Stripe payment. Params: session_id
booking_statusCheck booking status. Params: internal_ref
cancel_bookingCancel a booking. Params: internal_ref

Example Agent Prompt

"Find skip-the-line tours in Rome for 2 adults on April 20th.
Check availability and show me the best options with prices."
Sandbox by default
The MCP server runs in sandbox mode. All bookings are test-only. For production MCP access, contact us.

Response Codes

CodeMeaning
200Success
400Bad request — missing or invalid parameters
404Resource not found
422Unprocessable — provider error or business logic failure
500Internal server error