# VistaLink API - LLM Integration Brief Version v1 - Last updated 2026-10-01 VistaLink is a hotel intelligence API for AI agents. 650,000 properties, each indexed on its own photography, its guest reviews, and its location, so a query can be matched on all three. Available as an MCP server and a REST API at https://api.vistalink.com. > This file is a drop-in system-prompt brief. Paste it into your agent's > system prompt, Claude Project, Cursor `.cursorrules`, or any other > place you brief an LLM. It encodes every tool, parameter, error code, > and gotcha needed to integrate VistaLink without round trips to docs. --- ## No key needed to try it Searching and reading hotel details needs **no signup and no API key**. Anonymous calls hit the real catalogue (the same index paid keys use), read-only, rate-limited to 10 requests/minute and 200 requests/day per caller, and a key-less search returns at most 20 hotels. On 429, honour `Retry-After`. - Anonymous: `search_hotels`, `get_hotel_details` (MCP) and `POST /v1/search`, `GET /v1/hotels/{hotel_id}` (REST) - Needs a key: `chat_about_hotels`, guest profiles, usage and billing endpoints - OpenAPI spec (public, lists the public /v1 routes): https://api.vistalink.com/openapi.json - MCP tools carry titles and annotations (`search_hotels` and `get_hotel_details` are read-only), and the server ships an in-chat hotel widget (MCP Apps) that ChatGPT and Claude render alongside the results. MCP config with no key (Claude Desktop, Cursor, any streamable-HTTP host): ```json { "mcpServers": { "vistalink": { "url": "https://api.vistalink.com/mcp" } } } ``` Claude Code: `claude mcp add --transport http vistalink https://api.vistalink.com/mcp` curl with no key: ```bash curl -X POST https://api.vistalink.com/v1/search \ -H "Content-Type: application/json" \ -d '{"city":"Lisbon","country":"PT","check_in":"2026-11-12","check_out":"2026-11-15","currency":"EUR","limit":5}' curl "https://api.vistalink.com/v1/hotels/HOTEL_ID?check_in=2026-11-12&check_out=2026-11-15¤cy=EUR" ``` ### Prices Pass **both** `check_in` and `check_out` (YYYY-MM-DD) and each hotel may carry a `price`: the all-inclusive total (taxes and fees included) for exactly that stay, for one room, in the `currency` you asked for. It is returned only when a price for those exact dates was observed in the last 72 hours; otherwise `price` is null. Without dates there is no price. The rules are the same with or without a key. ```json "price": { "total": 612.4, "currency": "EUR", "includes_taxes_and_fees": true, "check_in": "2026-11-12", "check_out": "2026-11-15", "nights": 3, "adults": 2, "rooms": 1, "source_total": 612.4, "source_currency": "EUR", "observed_at": "2026-10-01T06:12:00Z" } ``` `total` covers the whole stay, not a nightly rate. Show it with its dates and `observed_at`; the hotel's price at booking time is what counts. VistaLink does not take bookings inside your agent. If you are a consumer-facing assistant and the user wants to look at or book a hotel, link them to its public page at https://meetparley.ai/{locale}/hotel/{hotel_id} (e.g. `en` for {locale}). The page is public; chatting and booking there require the user to sign in. When you need more than the anonymous limit, or `chat_about_hotels`, create a free key at https://vistalink.com/developers. --- ## Coming soon Not available yet - do not call these. On the way: - **Account linking** - users sign in with their Parley account from ChatGPT or Claude. - `get_rates` - live rates for the dates the user has chosen. All-inclusive (taxes and fees included), in whatever currency the user needs. - `direct_book` - book directly with the hotel. Booking completes on the hotel's own payment page via a Parley checkout link; VistaLink never takes payment. Until then, send a user who wants to book to the hotel's public page (see above). Parameters will be announced in this file when they ship. --- ## Authentication Search and hotel details work without a key (see above). Everything else, and higher limits, needs a Bearer token: ``` Authorization: Bearer vl_live_YOUR_API_KEY ``` - No key - anonymous, read-only search and details on the real catalogue, 10 requests/minute and 200 requests/day per caller - `vl_test_*` - issued to Free-tier accounts; the real catalogue (not a sandbox) at Free-tier limits - `vl_live_*` - issued to Pro and Enterprise accounts; the same catalogue at your plan's limits, metered Both key types query the same index. The prefix tells you which kind of account issued the key; it does not switch to a different dataset. Generate keys at https://vistalink.com/developers/dashboard. Keys are shown **once at creation** - store them in an environment variable or secret manager. ### Recommended: environment variables ```bash export VISTALINK_API_KEY="vl_live_YOUR_API_KEY" ``` Then reference via env in your MCP config or HTTP client. Never inline a live key in source files. --- ## MCP Configuration VistaLink exposes an HTTP-Streamable MCP server at https://api.vistalink.com/mcp. No npm package required. Drop the block below into your MCP client config: ### Claude Code (CLI) One command, no config file: ```bash claude mcp add --transport http vistalink https://api.vistalink.com/mcp \ --header "Authorization: Bearer ${VISTALINK_API_KEY}" ``` Verify with `claude mcp list` - the three tools auto-discover in any session. ### Claude Desktop File path: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` - Linux: `~/.config/Claude/claude_desktop_config.json` ```json { "mcpServers": { "vistalink": { "url": "https://api.vistalink.com/mcp", "headers": { "Authorization": "Bearer vl_live_YOUR_API_KEY" } } } } ``` After saving, **restart Claude Desktop**. Then ask Claude "what tools are available?" - it should list `search_hotels`, `chat_about_hotels` and `get_hotel_details`. ### Cursor File path: `.cursor/mcp.json` at the workspace root (or `~/.cursor/mcp.json` globally). ```json { "mcpServers": { "vistalink": { "url": "https://api.vistalink.com/mcp", "headers": { "Authorization": "Bearer vl_live_YOUR_API_KEY" } } } } ``` ### Custom agents (any MCP-compatible host) Point at https://api.vistalink.com/mcp and pass `Authorization: Bearer `. Tool discovery is automatic. --- ## Scope - what VistaLink is and isn't for VistaLink is **only** for hotel accommodation: searching hotels and getting their details. It does **not** handle: - Flights, trains, transit - Restaurants, attractions, things to do - General destination advice or trip planning - Visa, weather, currency conversion For anything outside hotel accommodation, answer the user directly; do not call VistaLink. --- ## Tools (decision tree) Three tools, used in this typical order: ``` User intent | |- Concrete filters (city + dates/budget/amenities)? -> search_hotels |- Subjective / multi-turn / vibe? -> chat_about_hotels |- Specific hotel user already picked? -> get_hotel_details |- Anything else -> handle locally ``` If the user gives one subjective criterion ("a quiet hotel"), prefer `chat_about_hotels` over `search_hotels`. ### 1. search_hotels (POST /v1/search) - $0.01 / call Fast, structured, no LLM. <40ms response. No key needed (max 20 results key-less). With `check_in` + `check_out`, hotels carry an all-inclusive stay `price` (see "Prices"). See per-tool reference for the full parameter list (city, country, latitude, longitude, dates, guests, budget_min/max, currency, amenities, vibe, hotel_name, limit, include_rates). ### 2. chat_about_hotels (POST /v1/chat) - $0.03 / call LLM-powered conversation. Required: `message`. Optional: `session_id`, `guest_id`, `currency`, `locale`, `user_location`, `context` (free-form JSON of guest preferences), `clarification_id` + `clarification_option_id` (for clarification replies). Returns `hotels[]`, `references[]`, `pois[]`, `routes[]`, `usage`, and optionally `clarification_pending` and `segments[]`. Each hotel in `hotels[]` includes `hero_image_url` (best-fit image URL), `hero_image_label` (label for that image), `explanation` (why recommended), `explanation_factors[]` (ranked signals), `poi_distances` (metres to each queried POI), and `routes_to_pois` (walk/drive detail). `explanation` and `segments[]` are conditionally present — see the per-tool reference for conditional-presence rules. **Always reuse `session_id`** on follow-ups to retain context. **Always handle `clarification_pending`**: when present, surface the question + options to the user and call again with `clarification_id` and the chosen `clarification_option_id` (or a free-text `message` if `allow_free_text` is true). ### 3. get_hotel_details (GET /v1/hotels/{hotel_id}) - $0.005 / call Required: `hotel_id`. Optional: `currency`, `check_in` + `check_out` (for an all-inclusive stay `price`), `guests`, `rooms`. Returns full profile including `phone_number`, all images, all rooms with offers, reviews, and `guest_insights` (highlights + sentiment aspects). --- ## MCP Resources In addition to tools, the MCP server exposes two read-only resources: - `hotel://{hotel_id}` - same payload as get_hotel_details, fetchable by URI - `amenities://catalog` - canonical amenity codes for the `amenities` parameter Validate user-provided amenity language against `amenities://catalog` before passing to `search_hotels` - unknown codes are silently dropped. --- ## MCP Prompts Two prompt templates are also exposed: - `search_wizard` - structured follow-up flow when the user's first message is ambiguous - `comparison_template` - side-by-side compare of multiple hotels using get_hotel_details --- ## REST API Base URL: https://api.vistalink.com | Method | Path | Tool / purpose | |--------|------|----------------| | POST | /v1/search | search_hotels | | POST | /v1/chat | chat_about_hotels | | GET | /v1/hotels/{hotel_id} | get_hotel_details | | POST | /v1/guests | Create guest profile | | GET | /v1/guests/{guest_id} | Get guest profile | | PATCH | /v1/guests/{guest_id} | Update guest profile | | DELETE | /v1/guests/{guest_id} | Delete guest profile (GDPR) | | POST | /v1/keys | Create API key (returned once) | | GET | /v1/keys | List API keys (masked) | | DELETE | /v1/keys/{prefix} | Revoke API key | | GET | /v1/usage | Usage summary for current month | | GET | /v1/sessions/{session_id} | Chat session metadata + history | | GET | /v1/billing/invoices | Invoice history | Same auth header, same response shapes as MCP. --- ## Errors All errors return: ```json { "error": { "code": "rate_limited", "message": "..." } } ``` | Status | Codes | Notes | |--------|-------|-------| | 400 | bad_request | Malformed body, invalid types | | 401 | unauthorized | Invalid API key, or no key on a tool that needs one (anything except search_hotels / get_hotel_details) | | 403 | forbidden | Account not in good standing | | 404 | not_found | hotel_id, session_id, call_id, or guest_id doesn't exist | | 429 | rate_limited | Per-minute or monthly cap hit. Use `Retry-After` (seconds). | | 502 | upstream_error | Search infra error - retry with exponential backoff | | 503 | unavailable | Database or upstream down - retry later | ## Rate-limit headers (on every response) | Header | Meaning | |--------|---------| | X-RateLimit-Limit | Requests per minute allowed | | X-RateLimit-Remaining | Requests left in current minute window | | X-RateLimit-Reset | Unix timestamp when window resets | | X-Monthly-Quota-Limit | Monthly quota (if any) | | X-Monthly-Quota-Remaining | Monthly requests left | | X-Monthly-Quota-Reset | ISO 8601 timestamp of next reset | | Retry-After | (On 429 only) Seconds to wait before retry | --- ## Pricing | Tool | Cost | |------|------| | search_hotels | $0.01 / call | | get_hotel_details | $0.005 / call | | chat_about_hotels | $0.03 / call | ## Tiers | Tier | Per minute | Per month | Tools | |------|-----------|-----------|-------| | No key | 10 | - (200 per day) | search_hotels, get_hotel_details (max 20 results) | | Free | 5 | 50 | all three | | Pro | 120 | 1,000 incl. + PAYG | all three | | Enterprise | 500 | unlimited | all three | --- ## Common pitfalls 1. **Skipping `clarification_pending`.** If the engine asks for clarification, you must respond with `clarification_id` + a chosen `clarification_option_id` (or a free-text `message`) on the next call. Treating it as empty results loses the session. 2. **Not reusing `session_id`.** Each chat call without `session_id` starts a fresh conversation. The user's prior turns are gone. 3. **Ignoring `Retry-After` on 429.** Retrying immediately will fail again. Use the header value (seconds) or back off exponentially from `X-RateLimit-Reset`. 4. **Inlining keys.** Always read `VISTALINK_API_KEY` from environment. Never commit a `vl_live_*` key. 5. **Assuming amenity codes.** "hot tub", "WiFi", "kid friendly" are silently ignored if they don't match canonical codes. Validate against `amenities://catalog`. 6. **Treating empty hotels as an error.** `{"hotels": [], "total": 0}` means no matches - suggest broader criteria. --- ## Multi-turn chat example Turn 1 (new session): ```json { "name": "chat_about_hotels", "input": { "message": "Looking for a quiet boutique hotel in Paris under 250 EUR a night" } } ``` Response carries `session_id: "5b3e..."`, a few hotels, and a message. Turn 2 (same session - compare two of them): ```json { "name": "chat_about_hotels", "input": { "message": "Between Hotel Le Marais and Hotel Bristol, which has better breakfast reviews?", "session_id": "5b3e..." } } ``` The engine knows which two hotels the user means because they were in the prior turn. --- ## Status and support - Dashboard: https://vistalink.com/developers/dashboard - Status page: https://status.vistalink.com - Contact: contact@vistalink.com - Docs: https://vistalink.com/developers