Build AI agents that fetch live hotel rates with BusinessHotels.com
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.
/chat/completionssonar, sonar-pro/mcp-server.php?route=tools
Run this in your terminal before writing any code. No API key needed — the test key is included.
Win + X → Terminal 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
^ 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\"}"
'curl' is not recognized, curl is available in Windows 10 build 1803+. Try running from PowerShell instead.
| 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
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.
{
"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
}
Visit Open Perplexity (Settings → </> API)
and create an API key. The Sonar API supports function calling on both sonar and sonar-pro models.
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"]
}
}
See the full code examples below. The pattern is always: Perplexity → your handler → BusinessHotels MCP → back to Perplexity.
The integration follows a standard function-calling loop. Perplexity decides when to call the tool; your code executes the actual API call.
finish_reason: tool_calls
/chat/completions) supports custom function calling with the OpenAI-compatible tool format used in this guide. Use sonar or sonar-pro as the model.
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?"))
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);
curl and jq — brew 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'
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))
float(str(price).replace(",","")) before any mathhotel_id in session for follow-up questions without re-queryingppn_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."
hotels[] array — one hotelName string per call onlydisplay_all_in_total as a float — it's a comma-formatted stringppn_bundle token — it's embedded in the booking URLAll requests to the BusinessHotels MCP endpoint require an X-API-KEY header.
| Key | Use | Rate Limit |
|---|---|---|
test-live-hotel-rates2025 | Development & testing | 100 req/hour |
| Your production key | Live applications | Contact for limits |
| Parameter | Type | Required | Description |
|---|---|---|---|
hotelName | string | ✅ Yes | Hotel name + city + country code (e.g., "Hilton Garden Inn, Seattle, US") |
checkinDate | string | ✅ Yes | Check-in date — YYYY-MM-DD format |
checkoutDate | string | ✅ Yes | Check-out date — YYYY-MM-DD format |
adults | integer | No (default: 2) | Number of guests (1–4) |
currency | string | No (default: USD) | ISO 4217 currency code (USD, EUR, GBP, etc.) |
| Field | Type | Description |
|---|---|---|
hotel_name | string | Matched hotel property name |
address | string | Full property address |
display_all_in_total | float | Total price including all taxes & fees |
display_currency | string | Currency code for the price |
booking_page_live_rates | string | Direct booking URL — rate locked for ~20 min |
latitude | float | Property latitude |
longitude | float | Property longitude |
best_match_score | float | Match confidence 0–1. Ask user to confirm if < 0.85 |
response_time_ms | integer | API response time in ms |
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 | Cause | Recovery |
|---|---|---|
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 |
{
"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
}
YYYY-MM-DDbest_match_score — confirm with user if < 0.85booking_page_live_rates prominentlyX-API-KEY server-side onlyX-API-KEY in client-side JSsuggestions[] array on errors