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.
http://localhost:3017JSONNone requiredSandbox 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:
/api/search/autocompleteLow-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
| Name | Type | Required | Description |
|---|---|---|---|
q | string | Yes | Search query (min 2 chars) |
lang | string | No | Language code for localized names (default: en) |
limit | number | No | Cap per bucket (default: 5, max: 10) |
providers | string | No | Comma-separated provider IDs (2=Tiqets, 3=Viator, 7=Headout). Scopes the activities bucket only — cities, pois, tags stay provider-agnostic. |
/api/search/destinationsSearch for cities and destinations by name.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | Yes | Search query (min 2 chars) |
lang | string | No | Language code (default: en) |
limit | number | No | Max results (default: 10, max: 20) |
/api/search/activitiesSearch activities by text, city, or filters. Returns paginated results with pricing.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Full-text search query |
city | number|string | No | City ID or slug/name (e.g. 84632 or rome). Unknown value returns empty. |
city_name | string | No | Deprecated alias for city when passing a name |
sort | string | No | rank (default, recommended ranking), caliber, rating, reviews, price_asc, price_desc |
page | number | No | Page number (default: 1) |
limit | number | No | Results per page (default: 20, max: 100) |
lang | string | No | Language code (default: en). Localizes name, description, url when a translation exists. |
currency | string | No | 3-letter currency code (default: EUR). Drives the price field. |
providers | string | No | Comma-separated provider IDs (2=Tiqets, 3=Viator, 7=Headout) |
min_rating | number | No | Minimum review rating |
min_price | number | No | Minimum price in the selected currency's minor unit (cents for EUR/USD) |
max_price | number | No | Maximum price in the selected currency's minor unit |
tags | string | No | Comma-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. |
pois | string | No | Comma-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. |
features | string | No | Comma-separated characteristic keys — AND semantics, e.g. free_cancellation,mobile_ticket. Supported: free_cancellation, instant_confirmation, mobile_ticket, accessible. Unknown tokens are dropped. |
tour_types | string | No | Comma-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. |
facets | boolean | No | Return 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
}
}/api/search/poisPoints 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
| Name | Type | Required | Description |
|---|---|---|---|
city | number|string | Yes | City ID or slug (e.g. 87000 or florence). Alias: cityId. Unknown value returns empty. |
type | string | No | attraction, district, or landmark (default: all) |
category | string | No | Category id or slug, CSV for multiple (e.g. museums,theme-parks). Each POI also returns its category object. |
q | string | No | Name or slug substring filter |
limit | number | No | POIs to return (default: 20, max: 100) |
withActivities | boolean | No | When true, embeds top activities per POI (sorted by caliber) |
activitiesLimit | number | No | Per-POI activity cap when withActivities=true (default: 5, max: 20) |
lang | string | No | Language code for localized POI names (default: en) |
/api/search/product/:idGet full product detail including description, gallery, itinerary, pricing, features, and cancellation policy.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | number | Yes | Activity ID (URL param) |
lang | string | No | Language code (default: en) |
/api/search/product/:id/reviewsPaginated reviews for a single activity.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | number | Yes | Activity ID (URL param) |
page | number | No | Page number (default: 1) |
limit | number | No | Reviews per page (default: 10, max: 50) |
sort | string | No | recent (default), rating_desc, rating_asc |
minRating | number | No | Minimum star rating |
lang | string | No | Restrict to reviews written in this language |
/api/availability/scheduleGet a monthly availability calendar. Returns which dates have available slots.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
activityId | number | Yes | Activity ID |
dateFrom | string | Yes | Start date (YYYY-MM-DD) |
dateTo | string | Yes | End date (YYYY-MM-DD) |
currency | string | No | Currency code (default: EUR) |
/api/availability/slotsGet real-time bookable time slots for a specific date. Use after the user selects a date from the schedule.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
activityId | number | Yes | Activity ID |
date | string | Yes | Date (YYYY-MM-DD) |
adults | number | No | Number of adults (default: 2) |
children | number | No | Number of children (default: 0) |
currency | string | No | Currency code (default: EUR) |
/api/booking/optionsGet booking form fields: product variants, age bands, booking questions, language guides, and cancellation policy.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
activityId | number | Yes | Activity ID |
currency | string | No | Currency code (default: EUR) |
productOptionCode | string | No | Specific variant code (returns all if omitted) |
/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
| Name | Type | Required | Description |
|---|---|---|---|
activityId | number | Yes | Activity ID |
date | string | Yes | Travel date (YYYY-MM-DD) |
startTime | string | No | Time slot (HH:MM) or omit for ON_DEMAND |
productOptionCode | string | Yes | Product variant code (from booking/options) |
passengers | object|array | Yes | Either { adults, children?, infants?, seniors?, youth? } or [{ bandId: 'adult'|'child'|..., count }]. adults >= 1 is required. |
contact | object | Yes | { firstName, lastName, email, phone } |
travelers | array | No | Traveler details per person |
bookingAnswers | array | No | Answers to booking questions |
languageGuide | object | No | Selected language guide |
currency | string | No | Currency (default: EUR) |
baseUrl | string | No | Origin 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"
}/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
| Name | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Stripe 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"
}/api/booking/statusCheck the current status of a booking.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
internalRef | string | Yes | Internal booking reference (e.g. TS-3-abc123-def456). ref is accepted as an alias. |
/api/booking/cancelCancel a booking. Cancels with the provider and refunds the Stripe payment.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
internalRef | string | Yes | Internal booking reference. ref is accepted as an alias. |
/api/data/destinationsDiscover 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
| Name | Type | Required | Description |
|---|---|---|---|
q | string | No | Filter by city name/slug substring |
limit | number | No | Max scopes to return (default: 50, max: 500) |
cursor | number | No | Opaque pagination cursor returned as nextCursor |
/api/data/categoriesList the visible tag categories. For free-text tag search use /api/search/tags instead.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
type | string | No | activity_type or filter (default: both) |
lang | string | No | Language code for localized names (default: en) |
limit | number | No | Max categories (default: 200, max: 500) |
Stripe Payment Flow
The booking system uses Stripe's manual capture mode for safe payment handling:
- Hold —
POST /api/booking/holdcreates a Stripe Checkout session withcapture_method: manual. The customer's card is authorized but not charged. - Redirect — Redirect the customer to the returned
checkoutUrl. Stripe handles card input securely. - Success callback — After payment, Stripe redirects to your success URL with
?session_id=.... - Confirm — Call
POST /api/booking/confirmwith the session ID. This confirms with the provider, then captures the payment. - Failure — If the provider rejects the booking, the payment authorization is cancelled automatically (no charge).
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
| Tool | Description |
|---|---|
search_cities | Search for cities by name. Params: query, lang |
search_activities | Search activities by city name or text query. Params: city_name, query, providers, sort, limit |
get_product | Get full product detail. Params: activity_id, lang |
check_availability | Get available dates and time slots. Params: activity_id, date_from, date_to |
get_slots | Get bookable time slots for a date. Params: activity_id, date, adults |
get_booking_options | Get booking form fields (variants, questions). Params: activity_id |
hold_booking | Hold a booking and get Stripe checkout URL. Params: activity_id, date, start_time, product_option_code, adults, contact |
confirm_booking | Confirm after Stripe payment. Params: session_id |
booking_status | Check booking status. Params: internal_ref |
cancel_booking | Cancel 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."
The MCP server runs in sandbox mode. All bookings are test-only. For production MCP access, contact us.
Response Codes
| Code | Meaning |
|---|---|
200 | Success |
400 | Bad request — missing or invalid parameters |
404 | Resource not found |
422 | Unprocessable — provider error or business logic failure |
500 | Internal server error |