Search Africa safari destinations, generate day-by-day itineraries, and price expert travel work.
Maintain this server? Claim it
Call this server's tools from your browser. Nothing to install.
Requests go through mcptry because browsers can't call most MCP servers directly. Header values are sent to the server for that request only and are never stored or logged.
Tool list from our last check. Connect live to refresh it.
Add an activity to a specific day and time slot (morning/afternoon/evening). Optionally link to a content item using its type and id. Requires a bearer token.
Short title for the activity
The day UUID to add the activity to
Parent itinerary UUID
Source content id
Content type to link
This tool isn't marked read-only, so it may change something on the server or in an account you connected.
Add an activity to a specific day and time slot (morning/afternoon/evening). Optionally link to a content item using its type and id. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| slot* | string | |
| title* | string | Short title for the activity |
| day_id* | string | The day UUID to add the activity to |
| description | string | |
| itinerary_id* | string | Parent itinerary UUID |
| wp_content_id | integer | Source content id |
| wp_content_type | string | Content type to link |
| duration_minutes | integer |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id",
"day_id",
"slot",
"title"
],
"properties": {
"slot": {
"enum": [
"morning",
"afternoon",
"evening"
],
"type": "string"
},
"title": {
"type": "string",
"minLength": 1,
"description": "Short title for the activity"
},
"day_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The day UUID to add the activity to"
},
"description": {
"type": "string"
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Parent itinerary UUID"
},
"wp_content_id": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Source content id"
},
"wp_content_type": {
"enum": [
"itinerary",
"activity",
"destination",
"post"
],
"type": "string",
"description": "Content type to link"
},
"duration_minutes": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 5
}
}
}Append an extra day to a concierge itinerary and increment num_days. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| notes | string | |
| title | string | Optional day label, e.g. 'Drive to Hwange' |
| itinerary_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"notes": {
"type": "string"
},
"title": {
"type": "string",
"description": "Optional day label, e.g. 'Drive to Hwange'"
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
}
}Create a new (empty) itinerary attributed to the Iconic Expeditions concierge, made public and returned with its share link. Days are created automatically. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| title* | string | Itinerary title, e.g. '7-Day Hwange Safari' |
| num_days* | integer | Number of days |
| start_date | string | Optional ISO date string for day 1, e.g. '2025-06-01' |
| description | string | Optional short description |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"title",
"num_days"
],
"properties": {
"title": {
"type": "string",
"minLength": 1,
"description": "Itinerary title, e.g. '7-Day Hwange Safari'"
},
"num_days": {
"type": "integer",
"maximum": 30,
"minimum": 1,
"description": "Number of days"
},
"start_date": {
"type": "string",
"description": "Optional ISO date string for day 1, e.g. '2025-06-01'"
},
"description": {
"type": "string",
"description": "Optional short description"
}
}
}Remove an activity from a concierge itinerary day. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| activity_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"activity_id"
],
"properties": {
"activity_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
}
}Permanently delete a concierge itinerary and all its days and activities. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| itinerary_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
}
}Give at least 3 places plus optional traveller preferences and let the AI concierge build a full, bespoke day-by-day itinerary. It mines Iconic Expeditions content for real detail and synthesizes an ORIGINAL plan tailored to the guest (never a copy of a published itinerary). Attributed to the Iconic Expeditions account and made public. Returns immediately with the itinerary id + share link; poll get_itinerary for progress (generation_status: pending -> building -> complete). Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| title* | string | Itinerary title, e.g. 'Highlights of Zimbabwe' |
| places* | array | At least 3 place names, e.g. ['Victoria Falls', 'Hwange', 'Mana Pools'] |
| language | string | Language for day titles, notes, and activity text: 'en' (English), 'ru' (Russian), or 'zh' (Chinese). Defaults to English. |
| num_days* | integer | Number of days for the trip |
| preferences | string | Traveller preferences that should shape the plan: pace, interests, budget, travel style |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"title",
"places",
"num_days"
],
"properties": {
"title": {
"type": "string",
"minLength": 1,
"description": "Itinerary title, e.g. 'Highlights of Zimbabwe'"
},
"places": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 3,
"description": "At least 3 place names, e.g. ['Victoria Falls', 'Hwange', 'Mana Pools']"
},
"language": {
"enum": [
"en",
"ru",
"zh"
],
"type": "string",
"description": "Language for day titles, notes, and activity text: 'en' (English), 'ru' (Russian), or 'zh' (Chinese). Defaults to English."
},
"num_days": {
"type": "integer",
"maximum": 30,
"minimum": 1,
"description": "Number of days for the trip"
},
"preferences": {
"type": "string",
"description": "Traveller preferences that should shape the plan: pace, interests, budget, travel style"
}
}
}Fetch a single content item by type and id, walking the content chain (WordPress -> cache). Returns the item with its `source`.
| Argument | Type | Description |
|---|---|---|
| id* | integer | Source content id (WordPress post id) |
| type* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"type",
"id"
],
"properties": {
"id": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Source content id (WordPress post id)"
},
"type": {
"enum": [
"itinerary",
"activity",
"destination",
"post"
],
"type": "string"
}
}
}Get full details for a single destination by slug or id.
| Argument | Type | Description |
|---|---|---|
| identifier* | string | Either a destination id or its URL slug |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"identifier"
],
"properties": {
"identifier": {
"type": "string",
"description": "Either a destination id or its URL slug"
}
}
}Get a full itinerary with all days and activities, sorted by day number and time slot. Public itineraries are readable without a token; private ones require the owning token.
| Argument | Type | Description |
|---|---|---|
| itinerary_id* | string | The UUID of the itinerary |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The UUID of the itinerary"
}
}
}Read an itinerary's planning dossier: its legs, month-by-month seasonality, cost model, booking order, field notes and open choices. Public — no token needed. Returns nulls for blocks the itinerary doesn't have; older itineraries have none of them.
| Argument | Type | Description |
|---|---|---|
| itinerary_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
}
}List the paid concierge service tiers and their prices: itinerary verification, verification + full booking, and full guided service. Read-only and public — use this to quote a traveller before they buy.
No arguments.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {}
}Alias of get_content, kept for backwards compatibility. Prefer get_content.
| Argument | Type | Description |
|---|---|---|
| id* | integer | Source content id (WordPress post id) |
| type* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"type",
"id"
],
"properties": {
"id": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Source content id (WordPress post id)"
},
"type": {
"enum": [
"itinerary",
"activity",
"destination",
"post"
],
"type": "string"
}
}
}List destinations from the database, optionally filtered by region or category.
| Argument | Type | Description |
|---|---|---|
| limit | integer | |
| region | string | Filter by region name, e.g. 'Matabeleland North' |
| category | string | Filter by category, e.g. 'National Park' |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"limit": {
"type": "integer",
"default": 20,
"maximum": 50,
"minimum": 1
},
"region": {
"type": "string",
"description": "Filter by region name, e.g. 'Matabeleland North'"
},
"category": {
"type": "string",
"description": "Filter by category, e.g. 'National Park'"
}
}
}List itineraries created by the Iconic Expeditions concierge, most recent first. Requires a bearer token.
No arguments.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {}
}Price an itinerary's cost model against a selection: a comfort tier, any per-line choices, a trip length and a traveller count. Returns the per-person line breakdown and the group total. Public — no token needed. Amounts are in minor units. Call get_itinerary_dossier first for the tier and option ids.
| Argument | Type | Description |
|---|---|---|
| nights | integer | Defaults to the cost model's reference length |
| tier_id | string | Defaults to the cheapest tier |
| option_ids | array | One entry per cost option group you want to choose in |
| travellers | integer | |
| itinerary_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"nights": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1,
"description": "Defaults to the cost model's reference length"
},
"tier_id": {
"type": "string",
"description": "Defaults to the cheapest tier"
},
"option_ids": {
"type": "array",
"items": {
"type": "object",
"required": [
"group_id",
"option_id"
],
"properties": {
"group_id": {
"type": "string"
},
"option_id": {
"type": "string"
}
}
},
"description": "One entry per cost option group you want to choose in"
},
"travellers": {
"type": "integer",
"default": 1,
"maximum": 9007199254740991,
"minimum": 1
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
}
}Start a PAID booking service: verification PLUS we make all the bookings (lodges, transfers, activities, permits). Returns a Stripe checkout URL. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| notes | string | |
| itinerary_id* | string | |
| contact_email | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"notes": {
"type": "string"
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"contact_email": {
"type": "string",
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
}
}
}Start a PAID full-service, fully-guided expedition with Iconic Expeditions guides. This $500 is a deposit; the final custom price is quoted by an expert afterward based on the itinerary. Returns a Stripe checkout URL. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| notes | string | |
| itinerary_id* | string | |
| contact_email | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"notes": {
"type": "string"
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"contact_email": {
"type": "string",
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
}
}
}Start a PAID itinerary verification. Our travel experts manually confirm availability, seasonality, routing and current lodge rates, then return a feasibility report and final price. Returns a Stripe checkout URL. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| notes | string | Anything the experts should know (dates, group size, budget) |
| itinerary_id* | string | |
| contact_email | string | Where to send the report + receipt |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"notes": {
"type": "string",
"description": "Anything the experts should know (dates, group size, budget)"
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"contact_email": {
"type": "string",
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
"description": "Where to send the report + receipt"
}
}
}Search Iconic Expeditions content across itineraries, activities, destinations, and posts. Flows through the content chain (WordPress -> local cache -> AI fallback), so gaps WordPress doesn't cover are filled with clearly-labeled AI-synthesized results. Each item includes a `source` field (wordpress | cache | ai).
| Argument | Type | Description |
|---|---|---|
| type* | string | Content type to search |
| search | string | Search keyword |
| per_page | integer |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"type"
],
"properties": {
"type": {
"enum": [
"itinerary",
"activity",
"destination",
"post"
],
"type": "string",
"description": "Content type to search"
},
"search": {
"type": "string",
"description": "Search keyword"
},
"per_page": {
"type": "integer",
"default": 10,
"maximum": 20,
"minimum": 1
}
}
}Alias of search_content, kept for backwards compatibility. Prefer search_content.
| Argument | Type | Description |
|---|---|---|
| type* | string | Content type to search |
| search | string | Search keyword |
| per_page | integer |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"type"
],
"properties": {
"type": {
"enum": [
"itinerary",
"activity",
"destination",
"post"
],
"type": "string",
"description": "Content type to search"
},
"search": {
"type": "string",
"description": "Search keyword"
},
"per_page": {
"type": "integer",
"default": 10,
"maximum": 20,
"minimum": 1
}
}
}Write the planning layer above an itinerary's day-by-day. Every block is optional — send only the ones you mean to write. Each block you DO send REPLACES that block entirely, so restate it in full: a partial legs list silently deletes the rest of the legs. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| legs | array | The route's chapters. Every day must belong to exactly one leg, and legs must not overlap. |
| choices | array | Either/ors deliberately left open for the traveller |
| countries | integer | How many countries the route crosses |
| cost_model | object | Per-person breakdown lines, amounts per comfort tier, and any per-line choices |
| field_notes | array | Visas, certificates, luggage limits, kit |
| seasonality | object | One row per thing the trip is timed around, scored 0-3 across all twelve months, plus a verdict per month worth arguing about |
| itinerary_id* | string | |
| booking_order | array | What to book, in the order it has to happen |
Update metadata (title, description, dates, public status) on a concierge itinerary. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| title | string | |
| is_public | boolean | Set false to unpublish the shareable link |
| start_date | string | |
| description | string | |
| itinerary_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"title": {
"type": "string"
},
"is_public": {
"type": "boolean",
"description": "Set false to unpublish the shareable link"
},
"start_date": {
"type": "string"
},
"description": {
"type": "string"
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
}
}Write one day out at length — why it is shaped this way, what its timing depends on, what the alternatives cost — plus up to three callouts. Replaces that day's existing narrative and callouts. Requires a bearer token.
| Argument | Type | Description |
|---|---|---|
| callouts | array | |
| narrative* | string | Two or three short paragraphs, separated by blank lines. Plain text, no markup. |
| day_number* | integer | |
| itinerary_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id",
"day_number",
"narrative"
],
"properties": {
"callouts": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"title",
"body"
],
"properties": {
"body": {
"type": "string",
"minLength": 1
},
"kind": {
"enum": [
"alternative",
"swap",
"warning",
"note"
],
"type": "string"
},
"title": {
"type": "string",
"minLength": 1,
"description": "'Alternative', 'Swap worth considering'"
}
}
},
"maxItems": 3
},
"narrative": {
"type": "string",
"minLength": 1,
"description": "Two or three short paragraphs, separated by blank lines. Plain text, no markup."
},
"day_number": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
}
}{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"itinerary_id"
],
"properties": {
"legs": {
"type": "array",
"items": {
"type": "object",
"required": [
"title",
"start_day",
"end_day"
],
"properties": {
"title": {
"type": "string",
"minLength": 1,
"description": "Where this stretch is, in a word or two"
},
"end_day": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1
},
"summary": {
"type": "string",
"description": "A sentence or two on what these nights are for"
},
"start_day": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1
}
}
},
"minItems": 1,
"description": "The route's chapters. Every day must belong to exactly one leg, and legs must not overlap."
},
"choices": {
"type": "array",
"items": {
"type": "object",
"required": [
"title",
"options"
],
"properties": {
"title": {
"type": "string",
"minLength": 1,
"description": "'Pick your coast'"
},
"options": {
"type": "array",
"items": {
"type": "object",
"required": [
"label",
"body"
],
"properties": {
"body": {
"type": "string",
"minLength": 1,
"description": "What this option is actually like, and who should choose it"
},
"label": {
"type": "string",
"minLength": 1
},
"subtitle": {
"type": "string"
}
}
},
"minItems": 2
}
}
},
"minItems": 1,
"description": "Either/ors deliberately left open for the traveller"
},
"countries": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1,
"description": "How many countries the route crosses"
},
"cost_model": {
"type": "object",
"required": [
"currency",
"reference_nights",
"lines",
"tiers"
],
"properties": {
"lines": {
"type": "array",
"items": {
"type": "object",
"required": [
"key",
"label",
"scales_with_length"
],
"properties": {
"key": {
"type": "string",
"minLength": 1,
"description": "Short stable key used by tiers and option groups — 'land', 'permits', 'park_fees', 'flights'"
},
"label": {
"type": "string",
"minLength": 1
},
"scales_with_length": {
"type": "boolean",
"description": "True for nightly costs (camps, park fees), false for per-trip costs (permits, internal flights)"
}
}
},
"minItems": 1
},
"notes": {
"type": "string",
"description": "What the figures exclude and how firm they are"
},
"tiers": {
"type": "array",
"items": {
"type": "object",
"required": [
"label",
"amounts"
],
"properties": {
"label": {
"type": "string",
"minLength": 1,
"description": "'Lean', 'Mid-range', 'Luxury'"
},
"amounts": {
"type": "array",
"items": {
"type": "object",
"required": [
"line_key",
"amount"
],
"properties": {
"amount": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Per person, in minor units — 620000 for $6,200"
},
"line_key": {
"type": "string"
}
}
},
"minItems": 1,
"description": "One entry per cost line"
}
}
},
"minItems": 1
},
"currency": {
"type": "string",
"maxLength": 3,
"minLength": 3,
"description": "ISO 4217, lowercase — 'usd'"
},
"option_groups": {
"type": "array",
"items": {
"type": "object",
"required": [
"label",
"line_key",
"options"
],
"properties": {
"label": {
"type": "string",
"minLength": 1
},
"options": {
"type": "array",
"items": {
"type": "object",
"required": [
"label",
"amount"
],
"properties": {
"label": {
"type": "string",
"minLength": 1
},
"amount": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Per person, in minor units — 620000 for $6,200"
},
"is_default": {
"type": "boolean"
}
}
},
"minItems": 2
},
"line_key": {
"type": "string",
"minLength": 1,
"description": "Which cost line this choice overrides"
}
}
}
},
"length_options": {
"type": "array",
"items": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1
},
"description": "Shorter or longer variants offered to the reader, in nights"
},
"reference_nights": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1,
"description": "The trip length the amounts are quoted at"
}
},
"description": "Per-person breakdown lines, amounts per comfort tier, and any per-line choices"
},
"field_notes": {
"type": "array",
"items": {
"type": "object",
"required": [
"heading",
"body"
],
"properties": {
"body": {
"type": "string",
"minLength": 1
},
"heading": {
"type": "string",
"minLength": 1,
"description": "'Visas', 'Yellow fever', 'Luggage'"
}
}
},
"minItems": 1,
"description": "Visas, certificates, luggage limits, kit"
},
"seasonality": {
"type": "object",
"required": [
"rows",
"months",
"recommended_month"
],
"properties": {
"rows": {
"type": "array",
"items": {
"type": "object",
"required": [
"label",
"scores"
],
"properties": {
"label": {
"type": "string",
"minLength": 1,
"description": "What is being scored — a wildlife event, a trail condition, a sea state"
},
"scores": {
"type": "array",
"items": {
"type": "integer",
"maximum": 3,
"minimum": 0
},
"maxItems": 12,
"minItems": 12,
"description": "January first. 0 = avoid, 1 = poor, 2 = workable, 3 = peak"
}
}
},
"minItems": 1
},
"months": {
"type": "array",
"items": {
"type": "object",
"required": [
"month",
"rank",
"verdict",
"tags"
],
"properties": {
"rank": {
"enum": [
"primary",
"alternative",
"workable",
"avoid"
],
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 4,
"description": "Short labels: 'Peak crossings', 'Books out first', 'Avoid'"
},
"month": {
"type": "integer",
"maximum": 12,
"minimum": 1
},
"verdict": {
"type": "string",
"minLength": 1,
"description": "Why this month is or isn't the one, in two or three sentences"
}
}
},
"minItems": 1
},
"recommended_month": {
"type": "integer",
"maximum": 12,
"minimum": 1,
"description": "The month this plan is actually built around"
}
},
"description": "One row per thing the trip is timed around, scored 0-3 across all twelve months, plus a verdict per month worth arguing about"
},
"itinerary_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"booking_order": {
"type": "array",
"items": {
"type": "object",
"required": [
"title",
"detail"
],
"properties": {
"title": {
"type": "string",
"minLength": 1
},
"detail": {
"type": "string",
"minLength": 1,
"description": "Why it sits at this point in the order"
}
}
},
"minItems": 1,
"description": "What to book, in the order it has to happen"
}
}
}