---
title: "Ranked discovery search"
method: POST
path: "/search/stays"
---

# Ranked discovery search

`POST /search/stays`

## Request

- Base URL: `https://api.b2a.bluepillow.com/v1`
- URL: `https://api.b2a.bluepillow.com/v1/search/stays`
- Auth: HTTP bearer

## Headers

- `Idempotency-Key` string, uuid, required
- `B2A-Version` string, date

## Request body

- SearchRequest
  - `location` Location, required
    - `type` 'destination_id' | 'poi_id' | 'coordinates' | 'bbox' | 'property_id', required
    - `value` unknown, required
  - `dates` Dates, required
    - `check_in` string, date, required
    - `check_out` string, date, required
  - `guests` Guests, required
    - `adults` integer, required
    - `children_ages` integer[]
    - `rooms` integer
  - `filters` object
    - `price_max_eur` number
    - `min_rating` number
    - `property_types` string[]: Preferred property types. This steers the page rather than excluding: the requested types are boosted and a minority of other types can still appear. Read each result's property_type when the distinction matters.
  - `sort` 'score_desc' | 'price_asc' | 'rating_desc' | 'conversion_desc'
  - `page` object
    - `limit` integer
    - `cursor` string, nullable
  - `user_country` string, nullable
  - `language` string, nullable
  - `currency` string, nullable

## Response `200`

Search results

- SearchResponse
  - `results` Property[], required
    - `id` string, required
    - `name` string, required
    - `brand` string, nullable
    - `chain` string, nullable
    - `property_type` 'hotel' | 'aparthotel' | 'apartment' | 'villa' | 'bnb' | 'hostel' | 'resort', required
    - `rating` number, required
    - `stars` integer, nullable
    - `location` LocationObject, required
      - `lat` number, required
      - `lon` number, required
      - `address` string, required
      - `city` string, required
      - `country` string, required
      - `neighborhood` string, nullable
    - `amenities` string[], required
    - `images` ImageObject[]
      - `url` string, uri, required
      - `caption` string, nullable
    - `web_url` string, nullable: BluePillow property-page URL with the caller's search context (check-in/out, language, currency, party size) baked into query params. Links directly to the BluePillow listing page, not an OTA. None when the search context is too sparse to build a meaningful URL (e.g. single-property lookup without dates).
    - `description` string, nullable: Free-text property description from the canonical AccomodationIndex doc. Populated by the single-property endpoint; null when not available or on the search path.
    - `features` FeatureItem[]: Feature/facility tags from BP's AccomodationFeature list (e.g. non-smoking, wifi, pool). Populated by the single-property endpoint; empty on the search path (search-result payloads do not carry this field).
      - `code` string, required
      - `type` string, nullable
      - `name` string, nullable
    - `reviews_aggregate` ReviewsAggregate: rating_count and comment_count are two DISTINCT magnitudes — do not conflate. rating_count = reviews backing the score (OTA-published aggregate, e.g. 156). comment_count = readable review TEXTS available (the crawled subset, e.g. 53), fetchable via the reviews_sample / reviews_extended includes.
      - `score_0_5` number, required
      - `rating_count` integer, required
      - `comment_count` integer, nullable
      - `distribution_by_ota` object
    - `reviews_sample` ReviewSampleItem[], nullable: Recent review texts. On search, populated by include=reviews_sample (cap 5). On property detail, populated by include=reviews_sample (cap 5) or include=reviews_extended (cap 20). null when not requested; empty when requested but no review texts are available. See reviews_meta for the cap.
      - `score` number, required
      - `description` string, required
      - `language` string, nullable
      - `date` string, date-time, required
      - `connector_name` string, nullable
    - `reviews_meta` ReviewsMeta: Provenance for the reviews_sample list on property detail. Makes explicit that the list is deliberately capped (token efficiency), not a data limit: the aggregate rating already reflects ALL reviews, and the omitted texts are older.
      - `returned` integer, required
      - `total_available` integer, required
      - `capped` boolean, required
      - `cap` integer, required
      - `note` string, required
    - `ota_coverage` OTA[], required
    - `dedup_meta` DedupMeta
      - `source_listings` integer, required
      - `confidence` number, required
      - `last_reconciled` string, date-time, required
    - `offers` Offer[]: Live per-OTA offer list from BluePillow GET /search, sorted best-first. Populated only when the BP bridge is active and dates/guests were provided. Empty when no live data is available (Mongo fallback path).
      - `ota` string, required: Connector name (e.g. 'Booking', 'Agoda') as reported by BluePillow.
      - `amount` number, required
      - `amount_per_night` number, nullable
      - `currency` string, required
      - `breakfast_included` boolean
      - `refundable` boolean
      - `is_discounted` boolean
      - `discount_percentage` number, nullable
      - `deeplink_url` string, nullable: BluePillow skippy redirect URL (tracked redirect to the OTA booking page). Opens the OTA landing page for this property with dates/guests pre-filled, routing through BluePillow's tracking layer. Not a direct OTA link. None when BP did not return a URL for this offer.
      - `rooms_left` integer, nullable: Number of rooms remaining at this rate per BluePillow's live-availability fan-out. A low value (1-3) is a scarcity signal. Populated from /search when BP reports it; None when not available.
    - `price` Price
      - `amount` number, required
      - `amount_per_night` number, nullable
      - `currency` string, required
      - `breakfast_included` boolean
      - `refundable` boolean
      - `is_discounted` boolean
      - `discount_percentage` number, nullable
      - `ota` string, nullable
    - `availability_status` 'available' | 'unavailable' | 'unknown': Live availability state: available = BP confirmed bookable rooms; unavailable = BP reported no rooms for the requested dates; unknown = dates not provided or live fan-out was skipped.
  - `page` object, required
    - `next_cursor` string, nullable, required
    - `total_estimate` integer, required
  - `metadata` object, required
    - `price_as_of` string, date-time, required
    - `otas_queried` integer, required
    - `request_id` string, required

## Other responses

- `400`: Invalid request
- `401`: Auth missing
- `429`: Rate limited

---

[API](https://skmtc.dev/bluepillow/apis/b2a-api.md) · [All operations](https://skmtc.dev/bluepillow/apis/b2a-api/llms.txt) · [OpenAPI document](https://skmtc.dev/bluepillow/apis/b2a-api/revisions/1dfd82be92b6?raw)
