# VistaLink API - LLM Integration Brief Version v1 - Last updated 2026-05-20 VistaLink is a hotel intelligence API for AI agents. 350,820 properties indexed across visual aesthetics (47.2M analyzed photos), semantic guest review analysis (18.4M reviews, 18.7M extracted vibe signals), and contextual matching. 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. --- ## Authentication Every request carries a Bearer token: ``` Authorization: Bearer vl_live_YOUR_API_KEY ``` - `vl_live_*` - production keys, hit the full 350,820-property index, metered - `vl_test_*` - sandbox keys, hit a 5,000-hotel test dataset, free 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 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`, `get_hotel_details`, `call_hotel`. ### Cursor File path: `.cursor/mcp.json` at the workspace root. ```json { "vistalink": { "url": "https://api.vistalink.com/mcp", "env": { "VISTALINK_KEY": "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, getting details, and calling properties. 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) Four 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 |- User wants to phone the hotel? -> call_hotel |- 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. 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`, `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`. Returns full profile including `phone_number` (required before `call_hotel`), all images, all rooms with offers, reviews, and `guest_insights` (highlights + sentiment aspects). ### 4. call_hotel (MCP) - $0.50 flat + $0.05/min over 10 min Pro tier and above. Required: `hotel_id`, `phone_number`, `hotel_name`. See per-tool reference for the rest. Async: returns `call_id`. Poll `GET /v1/call/{call_id}/status` until `completed`, then `GET /v1/call/{call_id}/results` for the transcript and `structured_outputs`. Scenarios: `hotel_negotiation` (default), `hotel_confirm_booking`, `hotel_cancel_booking`. REST clients call the same playbook through `POST /v1/call` with the `scenario` field in the request body. Same auth scheme as MCP (`Authorization: Bearer`). --- ## 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/call | call_hotel (scenario in body) | | GET | /v1/call/{call_id}/status | Poll call status | | GET | /v1/call/{call_id}/results | Get call transcript + outcome | | 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. **Voice calls dispatch by scenario.** `POST /v1/call` is the single voice-call entry point. The `scenario` field in the request body picks the playbook: `hotel_negotiation` (default), `hotel_confirm_booking`, or `hotel_cancel_booking`. Poll `/v1/call/{call_id}/status` for state, then `/v1/call/{call_id}/results` for the transcript and structured outcome. --- ## Errors All errors return: ```json { "error": { "code": "rate_limited", "message": "..." } } ``` | Status | Codes | Notes | |--------|-------|-------| | 400 | bad_request | Malformed body, invalid types | | 401 | unauthorized | Missing / invalid API key | | 403 | forbidden | Account not in good standing OR free-tier user calling `call_hotel` | | 404 | not_found | hotel_id, session_id, call_id, or guest_id doesn't exist | | 409 | not_ready | call_hotel results requested before call completed | | 429 | rate_limited | Per-minute or monthly cap hit. Use `Retry-After` (seconds). | | 502 | upstream_error | Voice / 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 | | call_hotel | $0.50 flat (up to 10 min) + $0.05 / min over | ## Tiers | Tier | Per minute | Per month | Voice (call_hotel) | |------|-----------|-----------|--------------------| | Free | 5 | 50 | no | | Pro | 120 | 1,000 incl. + PAYG | yes | | Enterprise | 500 | unlimited | yes | Voice on free tier returns 403 with a clear upgrade message. --- ## 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. **Calling without details first.** `call_hotel` needs `phone_number` in E.164 - get it from `get_hotel_details`, don't trust user-typed numbers. 6. **Forgetting tier checks.** Voice calls return 403 for free tier. Surface a friendly upgrade prompt rather than a raw error. 7. **Assuming amenity codes.** "hot tub", "WiFi", "kid friendly" are silently ignored if they don't match canonical codes. Validate against `amenities://catalog`. 8. **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