🏨 Perplexity Agent API Integration

Build AI agents that fetch live hotel rates with BusinessHotels.com

⚡ ~650ms response 🌍 2M+ properties 💰 All-in pricing 🔓 Free test key 🤖 Function Calling

What Is This?

The BusinessHotels.com AI Hotel Finder gives your Perplexity agent real-time hotel rates, all-in pricing (taxes included), and direct booking URLs — all via a single function call. No scraping, no stale data, no rate card surprises.

Perplexity Built-in Search

  • Built-in web search tool
  • No custom functions
  • Cannot call external APIs ❌
  • Good for general Q&A only
  • Not suitable for live rates
✨ Key Features:
  • Live hotel rates from 2M+ properties worldwide
  • All-in pricing — taxes & fees always included
  • Direct booking URLs with ~20-min rate locks
  • ~650ms average response time
  • Free test key, no sign-up required
  • No extra Perplexity tool costs — standard token pricing only
View MCP / Universal Agentic Docs Perplexity Sonar API Docs
MCP discovery URL (for Perplexity Connectors, Claude, Cursor, etc.): /mcp-server.php?route=tools

⚡ Validate in 30 Seconds

Run this in your terminal before writing any code. No API key needed — the test key is included.

Windows 10/11: Press Win + XTerminal or Windows PowerShell. Response is auto-formatted — no extra tools needed.
# PowerShell — Windows 10/11 (auto-formats JSON response)
$body = @{
    hotelName    = "Marriott Marquis, San Francisco, US"
    checkinDate  = "2026-03-15"
    checkoutDate = "2026-03-17"
    adults       = 2
    currency     = "USD"
} | ConvertTo-Json

Invoke-RestMethod -Method POST `
  -Uri "https://www.businesshotels.com/mcp-server.php?route=tools/get_live_hotel_rates" `
  -Headers @{ "X-API-KEY" = "test-live-hotel-rates2025" } `
  -ContentType "application/json" `
  -Body $body
Windows CMD: Uses ^ for line continuation. Inner quotes escaped with \".
:: Windows Command Prompt
curl -s -X POST "https://www.businesshotels.com/mcp-server.php?route=tools/get_live_hotel_rates" ^
  -H "Content-Type: application/json" ^
  -H "X-API-KEY: test-live-hotel-rates2025" ^
  -d "{\"hotelName\":\"Marriott Marquis, San Francisco, US\",\"checkinDate\":\"2026-03-15\",\"checkoutDate\":\"2026-03-17\",\"adults\":2,\"currency\":\"USD\"}"
Tip: If you see 'curl' is not recognized, curl is available in Windows 10 build 1803+. Try running from PowerShell instead.
Mac / Linux / WSL / Git Bash: Add | python3 -m json.tool at the end to pretty-print. Backslash \ continues the line.
# Mac / Linux / WSL / Git Bash
curl -s -X POST \
  "https://www.businesshotels.com/mcp-server.php?route=tools/get_live_hotel_rates" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: test-live-hotel-rates2025" \
  -d '{
    "hotelName":    "Marriott Marquis, San Francisco, US",
    "checkinDate":  "2026-03-15",
    "checkoutDate": "2026-03-17",
    "adults":       2,
    "currency":     "USD"
  }' | python3 -m json.tool
🌐 Zero install — works anywhere: Open any browser → press F12 → click the Console tab → paste and press Enter.
// Paste in browser DevTools Console (F12 → Console tab)
fetch("https://www.businesshotels.com/mcp-server.php?route=tools/get_live_hotel_rates", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-KEY": "test-live-hotel-rates2025"
  },
  body: JSON.stringify({
    hotelName:    "Marriott Marquis, San Francisco, US",
    checkinDate:  "2026-03-15",
    checkoutDate: "2026-03-17",
    adults:       2,
    currency:     "USD"
  })
}).then(r => r.json()).then(data => {
  console.log("✅ Hotel:",   data.hotel_name);
  console.log("💰 Price:",   `$${data.display_all_in_total} ${data.display_currency}`);
  console.log("🔗 Book:",    data.booking_page_live_rates);
  console.log("📊 Score:",   data.best_match_score);
  console.log("Full response:", data);
});

Works on any webpage including businesshotels.com. The expandable response object lets you inspect every field interactively.

Expected Response

{
  "hotel_name":              "San Francisco Marriott Marquis",
  "address":                 "780 Mission St, San Francisco, CA 94103",
  "display_all_in_total":    389.50,
  "display_currency":        "USD",
  "booking_page_live_rates": "https://www.businesshotels.com/reservation.php?hotel-id=...",
  "latitude":                37.7842,
  "longitude":               -122.4016,
  "best_match_score":        0.97,
  "response_time_ms":        612
}

Performance

Typical
400–650ms
Peak load
≤ 800ms
Test key: Free, up to 100 requests/hour for development and agent tuning. ·  Light production: Suitable for low-volume agents and pilots. For higher limits or dedicated throughput, contact ai@businesshotels.com.

Quick Start

Step 1 — Get Your Perplexity API Key

Visit Open Perplexity (Settings → </> API) and create an API key. The Sonar API supports function calling on both sonar and sonar-pro models.

Step 2 — Define the Tool

Add this JSON tool definition to your /chat/completions request. The description is what tells Perplexity when to call it — keep it precise:

{
  "type": "function",
  "name": "get_live_hotel_rates",
  "description": "Use this tool whenever the user asks for live price,
    total cost with taxes and fees, availability, or a booking/checkout
    link for a specific hotel for given dates. Returns an all-in final
    price (including taxes) and a direct, price-locked booking URL.",
  "parameters": {
    "type": "object",
    "properties": {
      "hotelName": {
        "type": "string",
        "description": "Hotel name + city + 2-letter country code.
          Example: 'InterContinental Mark Hopkins, San Francisco, US'.
          Including location maximizes match accuracy."
      },
      "checkinDate": {
        "type": "string", "format": "date",
        "description": "Check-in date YYYY-MM-DD. Convert relative
          dates (e.g. 'next Friday') before calling."
      },
      "checkoutDate": {
        "type": "string", "format": "date",
        "description": "Check-out date YYYY-MM-DD. Must be after check-in."
      },
      "adults": {
        "type": "integer", "default": 2,
        "description": "Guests (1–4). Default 2 if not specified."
      },
      "currency": {
        "type": "string", "default": "USD",
        "description": "ISO 4217 code (USD, EUR, GBP…). Defaults to USD."
      }
    },
    "required": ["hotelName", "checkinDate", "checkoutDate"]
  }
}

Step 3 — Handle the Function Call & Return Results

See the full code examples below. The pattern is always: Perplexity → your handler → BusinessHotels MCP → back to Perplexity.

How It Works

The integration follows a standard function-calling loop. Perplexity decides when to call the tool; your code executes the actual API call.

User Natural language query "Rates for Hilton NYC, March 10-12?"
Perplexity Sonar Detects tool intent Returns finish_reason: tool_calls
Your Handler Extracts arguments Calls BusinessHotels MCP
BH MCP API Returns live rate Price + booking URL
Perplexity Sonar Formats answer Natural language response
⚠️ Important: The Sonar API (/chat/completions) supports custom function calling with the OpenAI-compatible tool format used in this guide. Use sonar or sonar-pro as the model.

Code Examples

Requirements: pip install requests — no Perplexity SDK needed.
import requests, json

# ── Configuration ─────────────────────────────────────────────
PERPLEXITY_API_KEY = "YOUR_PERPLEXITY_API_KEY"
BH_API_KEY         = "test-live-hotel-rates2025"
MODEL              = "sonar-pro"   # or "sonar" for lower cost
# ──────────────────────────────────────────────────────────────

tools = [{
    "type": "function",
    "name": "get_live_hotel_rates",
    "description": "Get live hotel rates with all-in pricing and booking URL",
    "parameters": {
        "type": "object",
        "properties": {
            "hotelName":    {"type": "string",  "description": "Hotel name with city and country"},
            "checkinDate":  {"type": "string",  "format": "date"},
            "checkoutDate": {"type": "string",  "format": "date"},
            "adults":       {"type": "integer", "default": 2},
            "currency":     {"type": "string",  "default": "USD"}
        },
        "required": ["hotelName", "checkinDate", "checkoutDate"]
    }
}]

def get_live_hotel_rates(**kwargs):
    """Call BusinessHotels MCP and return JSON result."""
    resp = requests.post(
        "https://www.businesshotels.com/mcp-server.php?route=tools/get_live_hotel_rates",
        headers={"Content-Type": "application/json", "X-API-KEY": BH_API_KEY},
        json=kwargs, timeout=10
    )
    resp.raise_for_status()
    return resp.json()

def ask_hotel_agent(user_query: str) -> str:
    messages = [{"role": "user", "content": user_query}]

    # Step 1: Send to Perplexity with tool definition
    r1 = requests.post(
        "https://api.perplexity.ai/chat/completions",
        headers={"Authorization": f"Bearer {PERPLEXITY_API_KEY}", "Content-Type": "application/json"},
        json={"model": MODEL, "messages": messages, "tools": tools, "tool_choice": "auto"}
    ).json()

    choice = r1["choices"][0]
    messages.append(choice["message"])

    # Step 2: Handle tool call if triggered
    if choice["finish_reason"] == "tool_calls":
        for tc in choice["message"]["tool_calls"]:
            args   = json.loads(tc["function"]["arguments"])
            result = get_live_hotel_rates(**args)

            # Check match confidence before proceeding
            if result.get("best_match_score", 1) < 0.85:
                return f"Low confidence match ({result['best_match_score']}). Did you mean: {result.get('suggestions', [])}"

            messages.append({
                "role":         "tool",
                "tool_call_id": tc["id"],
                "content":      json.dumps(result)
            })

        # Step 3: Get final natural-language answer
        r2 = requests.post(
            "https://api.perplexity.ai/chat/completions",
            headers={"Authorization": f"Bearer {PERPLEXITY_API_KEY}", "Content-Type": "application/json"},
            json={"model": MODEL, "messages": messages}
        ).json()
        return r2["choices"][0]["message"]["content"]

    return choice["message"]["content"]   # no tool call needed

# ── Example usage ─────────────────────────────────────────────
if __name__ == "__main__":
    print(ask_hotel_agent("Rates for Hilton San Francisco Union Square, Mar 10-12 2026?"))
Requirements: Node 18+ (native fetch) or npm install node-fetch for older versions.
// No SDK required — uses standard fetch API
const PERPLEXITY_API_KEY = "YOUR_PERPLEXITY_API_KEY";
const BH_API_KEY         = "test-live-hotel-rates2025";
const MODEL              = "sonar-pro";

const tools = [{
    type: "function",
    name: "get_live_hotel_rates",
    description: "Get live hotel rates with all-in pricing and booking URL",
    parameters: {
        type: "object",
        properties: {
            hotelName:    { type: "string",  description: "Hotel name with city and country" },
            checkinDate:  { type: "string",  format: "date" },
            checkoutDate: { type: "string",  format: "date" },
            adults:       { type: "integer", default: 2 },
            currency:     { type: "string",  default: "USD" }
        },
        required: ["hotelName", "checkinDate", "checkoutDate"]
    }
}];

async function getLiveHotelRates(params: Record<string, unknown>) {
    const res = await fetch(
        "https://www.businesshotels.com/mcp-server.php?route=tools/get_live_hotel_rates",
        { method: "POST", headers: { "Content-Type": "application/json", "X-API-KEY": BH_API_KEY }, body: JSON.stringify(params) }
    );
    return res.json();
}

async function pplxChat(messages: object[], withTools = false) {
    const body: Record<string, unknown> = { model: MODEL, messages };
    if (withTools) { body.tools = tools; body.tool_choice = "auto"; }
    const res = await fetch("https://api.perplexity.ai/chat/completions", {
        method: "POST",
        headers: { "Authorization": `Bearer ${PERPLEXITY_API_KEY}`, "Content-Type": "application/json" },
        body: JSON.stringify(body)
    });
    return res.json();
}

async function askHotelAgent(userQuery: string): Promise<string> {
    const messages: any[] = [{ role: "user", content: userQuery }];

    // Step 1: Ask Perplexity (with tools enabled)
    const r1     = await pplxChat(messages, true);
    const choice = r1.choices[0];
    messages.push(choice.message);

    // Step 2: Execute tool call if triggered
    if (choice.finish_reason === "tool_calls") {
        for (const tc of choice.message.tool_calls) {
            const args   = JSON.parse(tc.function.arguments);
            const result = await getLiveHotelRates(args);

            // Guard against low-confidence matches
            if ((result.best_match_score ?? 1) < 0.85) {
                return `Low confidence (${result.best_match_score}). Suggestions: ${(result.suggestions ?? []).join(', ')}`;
            }

            messages.push({ role: "tool", tool_call_id: tc.id, content: JSON.stringify(result) });
        }

        // Step 3: Return final answer
        const r2 = await pplxChat(messages);
        return r2.choices[0].message.content;
    }

    return choice.message.content;
}

// Example
askHotelAgent("Find rates for Marriott Marquis NYC, March 15-17 2026").then(console.log);
Requirements: curl and jqbrew install jq / apt install jq
#!/bin/bash
# Full end-to-end integration — paste and run after setting your key

PPLX_KEY="YOUR_PERPLEXITY_API_KEY"
BH_KEY="test-live-hotel-rates2025"
MODEL="sonar-pro"
USER_QUERY="Get rates for Four Seasons Boston, April 10-12 2026"

# ── Step 1: Send query to Perplexity with tool definition ──────
RESPONSE=$(curl -s -X POST "https://api.perplexity.ai/chat/completions" \
  -H "Authorization: Bearer $PPLX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "'"$MODEL"'",
    "tool_choice": "auto",
    "tools": [{
      "type": "function",
      "name": "get_live_hotel_rates",
      "description": "Get live hotel rates with taxes and booking URL",
      "parameters": {
        "type": "object",
        "properties": {
          "hotelName":    {"type": "string"},
          "checkinDate":  {"type": "string"},
          "checkoutDate": {"type": "string"},
          "adults":       {"type": "integer", "default": 2},
          "currency":     {"type": "string",  "default": "USD"}
        },
        "required": ["hotelName", "checkinDate", "checkoutDate"]
      }
    }],
    "messages": [{"role": "user", "content": "'"$USER_QUERY"'"}]
  }')

# ── Step 2: Check if tool was called ──────────────────────────
FINISH=$(echo "$RESPONSE" | jq -r '.choices[0].finish_reason')

if [ "$FINISH" != "tool_calls" ]; then
  echo "Direct answer (no tool call needed):"
  echo "$RESPONSE" | jq -r '.choices[0].message.content'
  exit 0
fi

# ── Step 3: Extract arguments from tool call ──────────────────
TC_JSON=$(echo  "$RESPONSE"  | jq -r '.choices[0].message.tool_calls[0]')
ARGS=$(echo     "$TC_JSON"   | jq -r '.function.arguments')
CALL_ID=$(echo  "$TC_JSON"   | jq -r '.id')
HOTEL=$(echo    "$ARGS"      | jq -r '.hotelName')
CHECKIN=$(echo  "$ARGS"      | jq -r '.checkinDate')
CHECKOUT=$(echo "$ARGS"      | jq -r '.checkoutDate')
ADULTS=$(echo   "$ARGS"      | jq -r '.adults // 2')
CURRENCY=$(echo "$ARGS"      | jq -r '.currency // "USD"')

# ── Step 4: Call BusinessHotels MCP API ──────────────────────
RATES=$(curl -s -X POST \
  "https://www.businesshotels.com/mcp-server.php?route=tools/get_live_hotel_rates" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: $BH_KEY" \
  -d "{
    \"hotelName\":    \"$HOTEL\",
    \"checkinDate\":  \"$CHECKIN\",
    \"checkoutDate\": \"$CHECKOUT\",
    \"adults\":        $ADULTS,
    \"currency\":     \"$CURRENCY\"
  }")

# Check match confidence
SCORE=$(echo "$RATES" | jq -r '.best_match_score // 1')
if (( $(echo "$SCORE < 0.85" | bc -l) )); then
  echo "⚠️  Low confidence match ($SCORE). Suggestions:"
  echo "$RATES" | jq -r '.suggestions[]?'
  exit 1
fi

# ── Step 5: Send tool result back to Perplexity ───────────────
ASSISTANT_MSG=$(echo "$RESPONSE" | jq -c '.choices[0].message')

FINAL=$(curl -s -X POST "https://api.perplexity.ai/chat/completions" \
  -H "Authorization: Bearer $PPLX_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"$MODEL\",
    \"messages\": [
      {\"role\": \"user\", \"content\": \"$USER_QUERY\"},
      $ASSISTANT_MSG,
      {\"role\": \"tool\", \"tool_call_id\": \"$CALL_ID\", \"content\": $(echo \"$RATES\" | jq -Rs .)}
    ]
  }")

echo "$FINAL" | jq -r '.choices[0].message.content'
Architecture Note: This API uses a one-hotel-per-request design. There is no batch endpoint. Loop through each hotel, collect all results, then present a single unified ranked response. Never respond mid-loop.
import requests, json

# ── Configuration ─────────────────────────────────────────────
PERPLEXITY_API_KEY = "YOUR_PERPLEXITY_API_KEY"
BH_API_KEY         = "test-live-hotel-rates2025"
MODEL              = "sonar-pro"
# ──────────────────────────────────────────────────────────────

def get_live_hotel_rates(**kwargs):
    """Call BusinessHotels MCP and return JSON result."""
    resp = requests.post(
        "https://www.businesshotels.com/mcp-server.php?route=tools/get_live_hotel_rates",
        headers={"Content-Type": "application/json", "X-API-KEY": BH_API_KEY},
        json=kwargs, timeout=10
    )
    resp.raise_for_status()
    return resp.json()

def compare_hotels(hotels: list, checkin: str, checkout: str, adults: int = 2, currency: str = "USD") -> str:
    params  = {"checkinDate": checkin, "checkoutDate": checkout, "adults": adults, "currency": currency}
    results = []
    sold_out = []

    # ── Loop through all hotels — collect BEFORE responding ────────
    for hotel in hotels:
        try:
            data  = get_live_hotel_rates(**{**params, "hotelName": hotel})
            rates = data.get("rates")

            # Low confidence match — skip and warn
            if data.get("best_match_score", 1) < 0.85:
                sold_out.append(f"{hotel} (low confidence match — skipped)")
                continue

            # Sold out or no inventory
            if not rates or not rates.get("display_all_in_total"):
                sold_out.append(f"{data.get('hotel_name', hotel)} (sold out)")
                continue

            # CRITICAL: strip commas before float conversion
            price = float(str(rates["display_all_in_total"]).replace(",", ""))
            results.append({
                "name":     data["hotel_name"],
                "address":  data.get("hotel_address", ""),
                "price":    price,
                "currency": rates["currency"],
                "url":      data["booking_page_live_rates"],
                "hotel_id": data["hotel_id"]   # store for session follow-ups
            })

        except Exception as e:
            sold_out.append(f"{hotel} (error: {e})")

    # ── Sort by price and build final response ─────────────────────
    if not results:
        return "No availability found for any of the selected hotels."

    results.sort(key=lambda x: x["price"])
    cheapest = results[0]

    # ── Format ranked response for user ───────────────────────────
    lines = [f"--- {checkin} to {checkout} · {adults} adults · All taxes & fees included ---\n"]
    for i, h in enumerate(results, 1):
        tag = " 🏆 BEST VALUE" if i == 1 else ""
        lines.append(f"{i}. {h['name']}: ${h['price']:,.2f}{tag}")

    lines.append(f"\n✅ Cheapest: {cheapest['name']} at ${cheapest['price']:,.2f} total")
    lines.append(f"👉 Book Now (rate locked ~20 min): {cheapest['url']}")

    if sold_out:
        lines.append("\n⚠️ Not available: " + ", ".join(sold_out))

    return "\n".join(lines)

# ── Example usage ─────────────────────────────────────────────
if __name__ == "__main__":
    sf_luxury = [
        "Fairmont San Francisco, San Francisco, US",
        "Four Seasons San Francisco at Embarcadero, San Francisco, US",
        "Ritz-Carlton San Francisco, San Francisco, US",
        "St. Regis San Francisco, San Francisco, US",
        "Palace Hotel a Luxury Collection Hotel, San Francisco, US"
    ]
    print(compare_hotels(sf_luxury, "2026-05-12", "2026-05-14", adults=2))
✅ Architectural Rules for Every LLM Agent
  • Call the endpoint once per hotel — there is no batch endpoint
  • Complete all requests before presenting results — never respond mid-loop
  • Always strip commas: float(str(price).replace(",","")) before any math
  • Store hotel_id in session for follow-up questions without re-querying
⚠️ Rate Lock Warning
The ppn_bundle token is valid for approximately 20 minutes from API response time. If time has elapsed before the user clicks Book Now, display: "This rate was fetched X minutes ago — prices may have changed. Refresh to confirm."
❌ Never Do This
  • Don't pass a hotels[] array — one hotelName string per call only
  • Don't treat display_all_in_total as a float — it's a comma-formatted string
  • Don't modify or expose the ppn_bundle token — it's embedded in the booking URL

API Reference

Authentication

All requests to the BusinessHotels MCP endpoint require an X-API-KEY header.

KeyUseRate Limit
test-live-hotel-rates2025Development & testing100 req/hour
Your production keyLive applicationsContact for limits

Function Parameters

ParameterTypeRequiredDescription
hotelNamestring ✅ Yes Hotel name + city + country code (e.g., "Hilton Garden Inn, Seattle, US")
checkinDatestring ✅ Yes Check-in date — YYYY-MM-DD format
checkoutDatestring ✅ Yes Check-out date — YYYY-MM-DD format
adultsinteger No (default: 2) Number of guests (1–4)
currencystring No (default: USD) ISO 4217 currency code (USD, EUR, GBP, etc.)

Response Fields

FieldTypeDescription
hotel_namestringMatched hotel property name
addressstringFull property address
display_all_in_totalfloatTotal price including all taxes & fees
display_currencystringCurrency code for the price
booking_page_live_ratesstringDirect booking URL — rate locked for ~20 min
latitudefloatProperty latitude
longitudefloatProperty longitude
best_match_scorefloatMatch confidence 0–1. Ask user to confirm if < 0.85
response_time_msintegerAPI response time in ms

Error Handling

⚠️ Always check best_match_score. If it's below 0.85, ask the user to confirm the hotel before showing prices or a booking link. A score of 0.62 means the API found something, but it may be the wrong property.

Error Responses

ErrorCauseRecovery
Hotel not found No matching property Use suggestions[] from response; ask user to clarify
Invalid date format Date not in YYYY-MM-DD Parse and reformat before calling
Check-out before check-in Invalid date range Validate dates client-side before calling
No availability Hotel fully booked Suggest alternate dates or nearby hotels
API timeout Request exceeded timeout Retry once with exponential backoff

Example Error Response

{
  "error":             "Hotel not found",
  "message":           "No matching property found for 'Hlton San Franciso'",
  "suggestions": [
    "Hilton San Francisco Union Square, San Francisco, US",
    "Hilton San Francisco Financial District, San Francisco, US"
  ],
  "best_match_score":  0.62
}

Best Practices

✅ Do's
  • Include city & 2-letter country code in hotel name
  • Convert relative dates ("tomorrow") to YYYY-MM-DD
  • Check best_match_score — confirm with user if < 0.85
  • Implement retry logic for timeouts (1 retry max)
  • Cache results for 5–10 minutes to reduce calls
  • Display booking_page_live_rates prominently
  • Keep X-API-KEY server-side only
❌ Don'ts
  • Don't omit city/country from hotel name
  • Don't call more than once per hotel+date combo in 5 min
  • Don't auto-book without confirming a low match score
  • Don't cache longer than 15 min (rates change)
  • Don't expose X-API-KEY in client-side JS
  • Don't ignore the suggestions[] array on errors

Pricing

BusinessHotels MCP API
FREE
No charge per call
Test key included
Perplexity Sonar API
Token-based
Standard rates apply
No surcharge for tools
Typical total per query
$0.001–$0.005
Token cost only
💡 Light production & beyond: The included test key is fine for development and low-volume agents. For higher rate limits or SLA-backed production use, contact ai@businesshotels.com.

Support & Resources

🎉 Built something cool? We'd love to feature your integration! Email ai@businesshotels.com with a demo and we'll share it with our developer community.