Appearance
POST /v1/concierge
Returns the concierge result set for a stay: the resolved hotel, nearby food and beverage venues, nearby attractions, and events happening during the stay. The response is a single JSON document.
http
POST /v1/concierge
Authorization: Bearer <token>
Content-Type: application/jsonRequest body
Send exactly the fields below. Any unknown field is rejected with 400 so typos surface immediately.
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
hotelPlaceId | string | no | — | Bare Overlook place id. When present, the hotel name fields are optional. |
hotelName | string | yes* | — | Name of the hotel. *Required unless hotelPlaceId is supplied. |
hotelAddress | string | no | "" | Street address; helps disambiguate resolution. |
hotelCity | string | yes* | — | City. *Required unless hotelPlaceId is supplied. |
hotelState | string | yes* | — | State/region code, e.g. "ME". *Required unless hotelPlaceId is supplied. |
checkinDate | string | yes | — | Date the stay begins. Must parse as a date. |
checkoutDate | string | yes | — | Date the stay ends; must be on or after checkinDate. |
roomRate | integer | no | 0 | Nightly rate in USD. Used as a price/quality signal. |
adults | integer | no | 1 | Number of adults. |
children | integer | no | 0 | Number of children. |
accessibleRoom | string | no | "no" | Accessible-room flag, e.g. "yes" / "no". |
loyaltyProgram | string | no | "no" | Loyalty-program flag, e.g. "yes" / "no". |
guestZip | string | no | "" | Guest ZIP code. |
packageName | string | no | "" | Rate package name, if any. |
specialNotes | string | no | "" | Free-text guest preferences; used as a relevance signal. |
externalGuestId | string | no | null | Your own opaque identifier for the guest. Echoed back unchanged; to be used for cross-booking personalization. |
resultsCounts | object | no | {} | Per-section result counts. Keys are foodAndBeverage, attractions, and events; each is an integer 0–5 (default 3). A count of 0 skips that section. |
radiusMiles | number | no | 15 | Search radius around the hotel, in miles (5–30). |
Numeric fields (roomRate, adults, children, radiusMiles, and each value inside resultsCounts) are parsed as numbers. A missing or empty field uses the default above; a value that is present but not a number is rejected with 400 invalid_request ("<field> must be a number").
externalGuestId is an opaque identifier that you assign to the guest — a CRM id, loyalty number, email address, or any other stable value. Send the same value on each of that guest's bookings; it is to be used for cross-booking personalization. Omit it when you do not track guests.
Validation
- Hotel identity: either
hotelPlaceId, or all ofhotelName/hotelCity/hotelState. - Missing or empty required fields →
400 invalid_request. - An unknown field →
400 invalid_request("Unknown field: <name>"). - An unparseable
checkinDateorcheckoutDate→400 invalid_request. checkoutDateearlier thancheckinDate→400 invalid_request.- A
resultsCountsthat is not an object, has an unknown section key, has a count outside0–5, or asks for zero results in every section →400 invalid_request. radiusMilesoutside5–30→400 invalid_request.roomRatebelow0,adultsbelow1, orchildrenbelow0→400 invalid_request.- A non-string
externalGuestId→400 invalid_request("externalGuestId must be a string"). - A body that is not valid JSON →
400 invalid_request.
Example request
json
{
"hotelName": "Portland Regency Hotel & Spa",
"hotelAddress": "20 Milk St",
"hotelCity": "Portland",
"hotelState": "ME",
"checkinDate": "2026-10-05",
"checkoutDate": "2026-10-10",
"roomRate": 300,
"adults": 1,
"children": 0,
"accessibleRoom": "no",
"loyaltyProgram": "no",
"guestZip": "04101",
"packageName": "",
"specialNotes": "",
"externalGuestId": "acme-guest-42",
"resultsCounts": { "foodAndBeverage": 3, "attractions": 3, "events": 3 }
}bash
curl -s https://api.overlookhospitality.com/v1/concierge \
-H "Authorization: Bearer $OVERLOOK_TOKEN" \
-H "Content-Type: application/json" \
-d @request.jsonResponse 200 OK
json
{
"requestId": "0f2f6c1e-3b6a-4b0e-9d0a-2c1f6b9e8a44",
"externalGuestId": "acme-guest-42",
"generatedAt": "2026-10-05T23:16:22.193Z",
"hotel": {
"placeId": "1a2b3c4d5e6f",
"name": "Portland Regency Hotel & Spa",
"label": "Portland Regency Hotel & Spa, Portland, ME",
"address": "20 Milk St",
"locality": "Portland",
"region": "ME",
"latitude": 43.657,
"longitude": -70.253
},
"foodAndBeverage": [
{
"placeId": "4484774505543785",
"name": "Scales Food",
"section": "foodAndBeverage",
"blurb": "Waterfront seafood house with a raw bar and harbor views.",
"why": "A special-occasion seafood dinner within a short walk of the hotel.",
"reasons": ["Date night", "Walkable", "Local favorite"],
"imageUrls": ["https://…"],
"imageUrl": "https://…",
"layers": ["michelin"],
"layerIcons": [{ "name": "Michelin Guides", "iconUrl": "https://…" }],
"latitude": 43.6567,
"longitude": -70.2531,
"aggregateRating": 4.5,
"reviewCount": 2103,
"address": "2 Portland Pier",
"locality": "Portland",
"region": "ME",
"priceRange": "$$$",
"website": "https://scalesfood.com/",
"reservationUrl": "https://www.opentable.com/r/scales-food-portland",
"distanceFromHotel": 0.1
}
],
"attractions": [
{
"placeId": "…",
"name": "Portland Museum of Art",
"section": "attractions",
"blurb": "…",
"why": "A compact, high-quality art museum that suits a rainy afternoon.",
"reasons": ["Rainy day", "Walkable"],
"imageUrls": ["https://…"],
"imageUrl": "https://…",
"layers": null,
"layerIcons": [],
"latitude": 43.6537,
"longitude": -70.2625,
"aggregateRating": 4.7,
"reviewCount": 812,
"address": "7 Congress Square",
"locality": "Portland",
"region": "ME",
"priceRange": null,
"distanceFromHotel": 0.4
}
],
"events": [
{
"eventId": "…",
"venuePlaceId": "…",
"title": "…",
"date": "2026-10-07",
"time": "19:30",
"venue": "…",
"blurb": "…",
"why": "Live music during your stay, a short ride from the hotel.",
"reasons": ["In your dates", "Free"],
"latitude": 43.65,
"longitude": -70.25,
"locality": "Portland",
"region": "ME",
"timezone": "America/New_York",
"address": "…",
"neighborhood": "Old Port",
"category": "music",
"url": "https://www.tixr.com/groups/example/events/live-jazz",
"isFree": false,
"ticketPrice": { "min": 25, "max": 45, "currency": "USD" },
"distanceFromHotel": 0.3
}
]
}The example above is abridged: with the default counts each section can contain up to 3 food and beverage venues, 3 attractions, and 3 events.
How results are chosen
Each section has its own count in the request's resultsCounts map: foodAndBeverage, attractions, and events (each 0–5, default 3). A count of 0 skips that section entirely — it is neither fetched nor selected, so the response section comes back empty. Each count is an upper bound: a section returns up to the number of items you ask for. Candidates are gathered from a search box of radiusMiles around the hotel, and distanceFromHotel is the straight-line distance in miles.
How pricing is blended
priceRange on a place is a relative band, never a dollar amount: $, $$, $$$, or $$$$. It is a single band and it is always Overlook's own blend, or null when we have no price signal for the place.
It is never a range, never an average, and never a third-party fallback.
Event list items carry isFree (true, false, or null for unknown) and a ticketPrice range ({ min, max, currency } or null), the same shape the event detail returns.
Top-level fields
| Field | Type | Description |
|---|---|---|
requestId | string | A UUID that identifies this request in logs. |
externalGuestId | string|null | The externalGuestId you sent, echoed back; null when you did not send one. |
generatedAt | string | ISO-8601 timestamp of when the result was produced. |
durationMs | integer | Total server-side processing time in milliseconds. |
hotel | Hotel | The resolved hotel reference. |
foodAndBeverage | Place[] | Ranked food and beverage venues. |
attractions | Place[] | Ranked attractions. |
events | Event[] | Ranked events occurring during the stay. |
See Data types for the shape of each item.
why / reasons
why and reasons are authored for each selected item by our concierge model and are always present, so clients can render them unconditionally.
Errors
All errors use the shared envelope and codes; see Errors for the full matrix. This endpoint's own code is 422 hotel_not_resolved, returned when the hotel cannot be confidently resolved to a place with coordinates.