# Overview (/docs) Stay22 turns travel intent on your site into commission revenue: a reader books a hotel or rental on Booking.com, VRBO, Expedia, Hotels.com, and more, and the booking is tracked to you. Choose the integration that fits how much control you need. ## Pick your path [#pick-your-path] Query live inventory and prices and build your own UI. Use this when you need full control. Start building with our MCP and agent skills Turn any destination into a monetized affiliate link. No code, no API key, free. Drop an iframe on your page and show an interactive map of nearby stays, tracking built in. ## Support [#support] Questions? Reach us at [support@stay22.com](mailto:support@stay22.com) # Quickstart (/docs/quickstart) ## Initial setup [#initial-setup] [Create a Stay22 Hub account](https://hub.stay22.com/en/auth/signup) and set your `AID` You're ready to build! Explore how to use [Allez](/docs/allez) or [Maps](/docs/maps) ## How you earn [#how-you-earn] Every integration carries your affiliate id (`aid`). When a user reaches a provider through Stay22 and books, the commission is tracked to your `aid` and paid in USD. # Familiarize Yourself (/docs/allez/familiarize) If you're a visual learner and want to see what Allez links look like in practice, this page is for you. Everything below is interactive. Remember, to your users, clicking on an allez link will look exactly the same as clicking on an OTA. This guide is for you to know what the different moving parts are. ## What does integration look like? [#what-does-integration-look-like] Below is a mock hotel listing page. Toggle between "Before" and "After" to see what changes when you wire up Allez links. Click any card in "After" mode to inspect the link that was built for it. ## Build your own link [#build-your-own-link] Want to experiment with specific parameters? Use the link builder to construct an Allez URL from scratch and see the output in real time. ## What to notice [#what-to-notice] * Every link starts with `https://www.stay22.com/allez/{provider}` * `aid` is always present. It's how we attribute bookings to you * `roam` lets us pick the best OTA. Try clicking the same roam link a few times. You might land on different providers * The `hotelname` parameter helps us match the right property on the provider's side * Dates, guests, and campaign are optional but improve the user experience and your tracking ## Next steps [#next-steps] * [Parameters](/docs/allez/parameters) - Every parameter you can use * [Roam](/docs/allez/roam) - How roam decides where to send users * [Providers](/docs/allez/providers) - Which OTAs Allez supports and how geo-routing works # Overview (/docs/allez) Allez is Stay22's universal affiliate redirect endpoint. You construct a URL, a user clicks it, and they land on the right page on the right OTA - with your affiliate tracking baked in. ## Why "Allez"? [#why-allez] We're proud of our Montreal roots. *Allez* (pronounced "ah-lay") is French for "go" - as in, go book that hotel. [Learn more about the word](https://www.frenchlearner.com/french-word-of-the-day/allez/) if you're curious. **Base URL:** `https://www.stay22.com/allez/{provider}` ## How it works [#how-it-works] You build a URL with your affiliate ID and a destination The user clicks the link Stay22 resolves the best landing page on the best provider optimized for the best chance of conversion The user lands on the providers page. Cookie activates. They make a purchase Purchases generate affiliate commissions tracked to your `aid` Profit Allez is free to use. ## Supported providers [#supported-providers] | Provider | Endpoint | Category | | ----------------------- | --------------------- | ------------------------------- | | **Roam** (AI-optimized) | `/allez/roam` | Universal | | Booking.com | `/allez/booking` | Accommodation, Activities, Cars | | Expedia | `/allez/expedia` | Accommodation, Cars, Flights | | Hotels.com | `/allez/hotelscom` | Accommodation | | Vrbo | `/allez/vrbo` | Accommodation | | Airbnb | `/allez/airbnb` | Accommodation | | Agoda | `/allez/agoda` | Accommodation | | TripAdvisor | `/allez/tripadvisor` | Accommodation | | Kayak | `/allez/kayak` | Metasearch | | GetYourGuide | `/allez/getyourguide` | Activities | Use `/allez/roam` as your default endpoint. Read more [here](/docs/allez/roam). ## Quick example [#quick-example] ``` https://www.stay22.com/allez/roam?aid=&address=bell%20centre%20montreal ``` That's a working Allez link. The `aid` identifies you, the `address` tells Allez where, and `roam` picks the best OTA for each user. ## Next steps [#next-steps] * [Quick Start](/docs/allez/quick-start) - Build your first link in 30 seconds * [Familiarize Yourself](/docs/allez/familiarize) - Interactive link builder and live examples * [Parameters](/docs/allez/parameters) - Complete parameter reference * [Searchbar](/docs/allez/searchbar) - For partners integrating accommodation search into their search boxes * [Retail](/docs/allez/retail) - Monetize product references with retail affiliation * [Providers](/docs/allez/providers) - Per-provider details and TLD routing * [Roam](/docs/allez/roam) - How `/allez/roam` works under the hood # Parameters (/docs/allez/parameters) All parameters are passed as URL query strings to `https://www.stay22.com/allez/{provider}`. ## Required [#required] Without an AID, there is no way for us to attribute a booking to you. ## Location [#location] Allez needs to know where you're linking to — anywhere on Earth. Provide a street address, coordinates, a hotel name, or a combination of these. If you have both `address` and `lat`/`lng`, coordinates take priority. The address is used as a fallback and for display context. ## Dates & Guests [#dates--guests] ## Tracking [#tracking] If you're using separators, please use `_` instead of `-` For example: `campaign=foo_bar` results in one string as "foo\_bar" If you use `campaign=foo-bar`, your reporting will have campaign strings "foo" and "bar" ## Localization [#localization] ## Provider & Routing [#provider--routing] See [Roam](/docs/allez/roam) for how `roam` selects a provider. ## See also [#see-also] * [Familiarize Yourself](/docs/allez/familiarize) - Build URLs interactively * [Providers](/docs/allez/providers) - Per-provider behavior and supported parameters * [Roam](/docs/allez/roam) - How the `roam` endpoint picks providers # Providers (/docs/allez/providers) Each provider endpoint handles the redirect to a specific OTA. Use `/allez/roam` to let the AI pick the best one, or target a provider directly. ## Accommodation providers [#accommodation-providers] ### Booking.com [#bookingcom] **Endpoint:** `/allez/booking` The most widely available provider globally. Supports hotel IDs, deep links, and address-based search. **Geo routing:** Booking.com uses a single TLD (`booking.com`) internationally, but Stay22 automatically sets the correct language and currency based on the user's location. ### Expedia [#expedia] **Endpoint:** `/allez/expedia` Supports 30+ regional TLDs. Stay22 automatically routes users to the correct one (e.g., `expedia.ca`, `expedia.co.uk`, `expedia.de`). ### Hotels.com [#hotelscom] **Endpoint:** `/allez/hotelscom` Part of the Expedia Group. Includes regional subdomains like `hoteles.com` (LATAM) and `hoteis.com` (Brazil). All handled automatically. ### Vrbo [#vrbo] **Endpoint:** `/allez/vrbo` Focused on vacation rentals. Has the most complex geo-routing of any provider: | User location | Redirected to | | ------------------ | --------------- | | US / International | vrbo.com | | France | abritel.fr | | Australia | stayz.com.au | | New Zealand | bookabach.co.nz | | Germany | fewo-direkt.de | Even if you hard-code a `vrbo.com` link, an Australian user will land on `stayz.com.au` with the same listing. ### Airbnb [#airbnb] **Endpoint:** `/allez/airbnb` Supports address-based search and hotel name matching. ### Agoda [#agoda] **Endpoint:** `/allez/agoda` Strong inventory in Asia-Pacific markets. ### TripAdvisor [#tripadvisor] **Endpoint:** `/allez/tripadvisor` Supports 30+ regional TLDs (e.g., `tripadvisor.fr`, `tripadvisor.ca`, `tripadvisor.co.uk`). Routed automatically based on user location. ## Other providers [#other-providers] ### Kayak [#kayak] **Endpoint:** `/allez/kayak` Metasearch engine. Supports all Kayak TLDs as well as sister sites: Momondo, HotelsCombined, Cheapflights, Swoodoo, checkfelix, and Mundi. The correct regional variant is selected automatically. ### GetYourGuide [#getyourguide] **Endpoint:** `/allez/getyourguide` Activities and experiences. Use the `address` parameter to search for activities near a destination. ## Geo-routing [#geo-routing] Stay22 automatically handles geo-routing for every redirect. You don't need to build different links for different regions - one URL works globally. When a user clicks an Allez link, Stay22 detects their location via IP and redirects them to the correct regional version of the OTA. This happens transparently - you always link to `stay22.com/allez/{provider}`, and Stay22 handles the rest. ### Language and currency [#language-and-currency] Even when a provider uses a single TLD (like Booking.com), Stay22 passes the correct language and currency based on the user's location. You can also set these explicitly: ``` https://www.stay22.com/allez/booking?aid=&address=paris&lang=fr¤cy=EUR ``` If you set `lang` or `currency`, they override the auto-detected values. ## Hotel name matching [#hotel-name-matching] When you pass the `hotelname` parameter, Stay22 performs fuzzy matching across its hotel database: ``` https://www.stay22.com/allez/roam?aid=&address=miami&hotelname=four%20seasons ``` The matching process: 1. Searches provider databases (Booking.com, Expedia, Hotels.com, Agoda) 2. Uses fuzzy string matching with a \~60% similarity threshold 3. Resolves the hotel's provider-specific ID for a direct deep-link Combine `hotelname` with `address` (city + country) for the best match accuracy. Just a hotel name without location context may return unexpected results. ## Provider selection by endpoint [#provider-selection-by-endpoint] | Endpoint | What happens | | ------------------- | -------------------------------------------------------------------- | | `/allez/roam` | AI picks the best provider per user. [Learn more](/docs/allez/roam). | | `/allez/booking` | Always goes to Booking.com | | `/allez/expedia` | Always goes to Expedia (with geo TLD) | | `/allez/{provider}` | Always goes to the named provider | ## See also [#see-also] * [Roam](/docs/allez/roam) - How `/allez/roam` picks the best provider * [Allez Parameters](/docs/allez/parameters) - Full parameter reference # Quick Start (/docs/allez/quick-start) This section is to help you understand the structure and different combinations of an allez link. If you only want to make one-off, single-use links, use the [Allez Generator](https://hub.stay22.com/en/allez) in your hub. ## The minimum viable link [#the-minimum-viable-link] You need two things: your affiliate ID and a destination. ``` https://www.stay22.com/allez/roam?aid=&address=paris%20france ``` That's a complete, functional Allez link. ## Three ways to specify location [#three-ways-to-specify-location] ### 1. Address (simplest) [#1-address-simplest] Pass a human-readable location in `address=`. City + country works, but the more specific the better. ``` https://www.stay22.com/allez/roam?aid=&address=bell%20centre%20montreal ``` ### 2. GPS Coordinates (most precise) [#2-gps-coordinates-most-precise] If you have lat/lng, use them - they skip the geocoding step and give the most accurate results. The params are `lat=` and `lng=` ``` https://www.stay22.com/allez/roam?aid=&lat=45.4960&lng=-73.5693 ``` ### 3. Direct OTA link [#3-direct-ota-link] Already have a specific Booking.com or Expedia URL? Pass it through `link=` and Allez handles the affiliate attribution. ``` https://www.stay22.com/allez/booking?aid=&link=https%3A%2F%2Fwww.booking.com%2Fhotel%2Fca%2Frenaissance-montreal-downtown.en-us.html ``` The `link` parameter must be URL-encoded. Use `encodeURIComponent()` in JavaScript. ## Add tracking [#add-tracking] Use the `campaign` parameter to distinguish where your clicks come from. You can add multiple campaigns. The biggest mistake you can make is not adding campaigns. If you want to know where each purchase came from, campaigns are the way to track them. If you have your analytics set up, you can append your user-tracking identifiers to the campaign, for re-targetting. ``` https://www.stay22.com/allez/roam?aid=&address=paris&campaign=blog_post_123&campaign=summer_2026 ``` ## Add dates and guests [#add-dates-and-guests] If you're building your own searchbox or itinerary planner, you need to know how many people in the party. Add them to the URL to give your users less to click on. This leads to better conversion rates. ``` https://www.stay22.com/allez/roam?aid=&address=paris&checkin=2026-07-01&checkout=2026-07-05&adults=2&children=1 ``` ## Next steps [#next-steps] * [Familiarize Yourself](/docs/allez/familiarize) - Build and test URLs interactively * [Parameters](/docs/allez/parameters) - All available query parameters * [Providers](/docs/allez/providers) - Per-provider details # Retail (/docs/allez/retail) Beyond hotel and accommodation bookings, Allez also supports retail product affiliation. If your content mentions products (gear, electronics, accessories, etc.), you can monetize those references too. **Base URL:** `https://www.stay22.com/allez/retail` ## How it works [#how-it-works] Instead of passing a location, you pass a product search term. We route the user to the best match in our retail affiliate network and handle the attribution. ``` https://www.stay22.com/allez/retail?aid=&search=noise%20cancelling%20headphones ``` The user lands on a product page with your affiliate tracking baked in, just like accommodation links. ## Parameters [#parameters] ## Examples [#examples] ### Basic product link [#basic-product-link] ``` https://www.stay22.com/allez/retail?aid=&search=travel%20backpack ``` ### With related keywords and price filter [#with-related-keywords-and-price-filter] ``` https://www.stay22.com/allez/retail?aid=&search=travel%20backpack&relatedKeywords=hiking,waterproof,40L&priceMin=50&campaign=gear_guide ``` ## Tips [#tips] * The more specific your `search` term, the better the match. "Sony WH-1000XM5" will outperform "headphones". * Use `relatedKeywords` to add context when the product name alone is ambiguous. * Retail links work alongside your accommodation links. A travel blog post can monetize both the hotel and the gear recommendations. ## See also [#see-also] * [Parameters](/docs/allez/parameters) - Accommodation parameter reference * [Quick Start](/docs/allez/quick-start) - Allez link basics # Roam (/docs/allez/roam) The `/allez/roam` endpoint uses a machine-learning model to pick the provider most likely to convert for each user, so you don't hard-code one. Stay22 supports a wide basket of OTAs — Booking.com, Expedia, Hotels.com, Vrbo, and more — and roam chooses among them per request. The model is trained on billions of annual clicks across all providers and predicts the best-converting provider for the destination, time, and user context. ## How it works [#how-it-works] ``` https://www.stay22.com/allez/roam?aid=&address=paris ``` When a user clicks a roam link: 1. Stay22 sees the destination, time of day, and other context 2. The model scores each available provider for that specific user 3. Country-specific weights adjust the scores (some providers convert better in certain regions) 4. The highest-scoring provider wins, and the user is redirected there ## Available providers in roam [#available-providers-in-roam] For accommodation, roam chooses between: * Booking.com * Expedia * Hotels.com * Vrbo * Agoda * and more coming soon . . The exact pool depends on availability - if a provider doesn't have inventory for the destination, it's excluded automatically. ## Controlling roam behavior [#controlling-roam-behavior] ### Exclude providers [#exclude-providers] To exclude specific providers from roam's selection: ``` https://www.stay22.com/allez/roam?aid=&address=paris&excludeproviders=vrbo,expedia ``` ### Force a specific provider (testing) [#force-a-specific-provider-testing] For debugging or A/B testing, override the AI selection while still using the roam endpoint: ``` https://www.stay22.com/allez/roam?aid=&address=paris&provider=booking ``` This forces the redirect to Booking.com but still goes through all the roam middleware (geo-routing, TLD selection, etc.). ### Two ways to enable roam [#two-ways-to-enable-roam] Roam activates when: * The endpoint is `/allez/roam` * OR the `roam=true` query parameter is set on any endpoint Roam requires a resolvable destination - it works with `address`, `lat-lng` as location parameters. It does **not** work with the `link` parameter (since you're already specifying the destination provider). ## Provider or roam? [#provider-or-roam] | Use case | Recommendation | | ------------------------------ | -------------------------------------------------------- | | General accommodation links | `/allez/roam` - let the AI optimize | | Affinity to a specific OTA | `/allez/{provider}` - hard-code the provider | | Testing conversion by provider | `/allez/roam?provider=X` - force a provider through roam | | Excluding underperforming OTAs | `/allez/roam?excludeproviders=X,Y` | ## See also [#see-also] * [Providers](/docs/allez/providers) - Per-provider details * [Allez Parameters](/docs/allez/parameters) - Full parameter reference # Searchbar (/docs/allez/searchbar) Some partners, especially transport providers, integrate accommodation search directly into their own search boxes. When a user toggles "Search accommodation" in their search bar, the results open via an Allez link as an undertab. A partner transport search bar with the accommodation-search toggle enabled For these integrations, we have a dedicated endpoint: **Base URL:** `https://www.stay22.com/allez/searchbar` ## How it differs from standard Allez [#how-it-differs-from-standard-allez] It doesn't, functionally. The searchbar endpoint accepts the same parameters and routes through the same routing logic as `/allez/roam`. The only difference is internal: searchbar links are tagged with separate tracking metadata so we can distinguish search box clicks from regular link clicks in reporting. This helps both you and us understand conversion by integration type. ## Quick example [#quick-example] ``` https://www.stay22.com/allez/searchbar?aid=&address=bell%20centre%20montreal&checkin=2026-08-15&checkout=2026-08-17 ``` That's it. Same parameters as a regular Allez link, different endpoint. ## When to use this [#when-to-use-this] Use `/allez/searchbar` when your integration is a search box or form where the user actively searches for accommodation, rather than clicking a pre-built link on a page. If you're building standard deep-links or embedding them in content, use the regular `/allez/{provider}` endpoint instead. ## Parameters [#parameters] All parameters from the [standard Allez reference](/docs/allez/parameters) apply here. There are no searchbar-specific parameters. ## See also [#see-also] * [Quick Start](/docs/allez/quick-start) - Allez link basics * [Parameters](/docs/allez/parameters) - Full parameter reference * [Roam](/docs/allez/roam) - How roam picks providers # Troubleshooting (/docs/allez/troubleshooting) ## Link redirects to unexpected provider [#link-redirects-to-unexpected-provider] **If using `/allez/roam`:** This is expected behavior - roam uses ML to pick the best provider for each user. If you need a specific provider, use `/allez/booking`, `/allez/expedia`, etc. **If using a specific provider:** Check that the provider name in the URL is correct. Valid values: `booking`, `expedia`, `hotelscom`, `vrbo`, `airbnb`, `agoda`, `tripadvisor`, `kayak`, `getyourguide`. ## Hotel not found / wrong hotel [#hotel-not-found--wrong-hotel] * **Add context:** Use `hotelname` together with `address` (city + country). Just a hotel name without location may match the wrong property. * **Check spelling:** Hotel name matching uses fuzzy matching (\~60% threshold), but very different spellings won't match. * **Use coordinates:** If you have the hotel's lat/lng, pass them via `lat` and `lng` - this is more reliable than name matching. * **Use hotel ID:** If you know the provider-specific hotel ID, pass it via `hid` for a direct lookup. * **Is the hotel still listed?** Check if the listing is still live on the OTA. ## Landing on wrong country/TLD [#landing-on-wrong-countrytld] Stay22 routes users based on their IP location. An Australian user clicking a Vrbo link will land on `stayz.com.au`, not `vrbo.com`. This is intentional - it improves conversion rates. If you need to override this, pass `lang` and `currency` parameters to hint at the desired locale. See [Providers](/docs/allez/providers) for geo-routing and TLD details. ## URL not working / 404 [#url-not-working--404] * **Check encoding:** Addresses with special characters must be URL-encoded. Use `encodeURIComponent()` in JavaScript or `urllib.parse.quote()` in Python. * **Check `link` parameter:** If using the `link` parameter, the entire URL must be URL-encoded, not just spaces. ```javascript // Correct const encoded = encodeURIComponent('https://www.booking.com/hotel/fr/le-marais.html'); const url = `https://www.stay22.com/allez/booking?aid=&link=${encoded}`; // Wrong - will break const url = `https://www.stay22.com/allez/booking?aid=&link=https://www.booking.com/hotel/fr/le-marais.html`; ``` ## Dates not pre-filled on the provider page [#dates-not-pre-filled-on-the-provider-page] * Check the format: dates must be `YYYY-MM-DD` (e.g., `2026-07-15`). * Dates must be in the future. ## Need help? [#need-help] Reach out to [support@stay22.com](mailto:support@stay22.com) or use the chat in [hub.stay22.com](https://hub.stay22.com). # Authentication (/docs/api/authentication) The Direct Travel API authenticates each request with an API key passed in the `X-API-KEY` header. No key is needed for demo mode, which is rate-limited to 5 requests per minute. ## Get an API key [#get-an-api-key] 1. Sign up or log in at [hub.stay22.com](https://hub.stay22.com/). 2. Go to [Settings → API](https://hub.stay22.com/en/settings/api), enter a token name, and click **Create**. ## Use your API key [#use-your-api-key] Pass the key in the `X-API-KEY` header (recommended): ```bash curl -H "X-API-KEY: " \ "https://api.stay22.com/v2/accommodations?address=Paris" # get at hub.stay22.com ``` Or as the `key` query parameter: ```bash curl "https://api.stay22.com/v2/accommodations?key=&address=Paris" ``` ## Demo mode [#demo-mode] Omit the key to try the API without an account. Demo mode is limited to **5 requests per minute** per IP; an API key raises this to 100. See [Rate Limits](/docs/api/rate-limits). ```bash curl "https://api.stay22.com/v2/accommodations?address=Paris" ``` ## Invalid or missing key [#invalid-or-missing-key] A missing or invalid key returns `401`: ```json { "statusCode": 401, "error": "Unauthorized", "code": "INVALID_API_KEY", "message": "Invalid API key. Get your API key at hub.stay22.com", "requestId": "550e8400-e29b-41d4-a716-446655440000" } ``` For the full list of error codes, see the [API Reference](/docs/api) — error responses are documented under each endpoint. # Overview (/docs/api) Starting with accommodations, receive commissions in USD anytime a reservation is made in any of the brands' platforms via Stay22. Get started by [Creating a new API token](https://hub.stay22.com/en/settings/api) in your Hub. ## Endpoints [#endpoints] * **[Accommodations](/docs/api/accommodations/search)** — the current unified search: one stay per property with a `suppliers` map across Booking.com, VRBO, Expedia, and Hotels.com, returned in a single request. Build your own travel UI and earn commissions on every booking tracked to your `aid`. * **[Reporting](/docs/api/reporting/transactions)** — retrieve partner transaction data. ## Try it now [#try-it-now] No key needed for demo mode (5 req/min). Open [Search Accommodation](/docs/api/accommodations/search) for the full parameter list and a live playground. ## Usage restrictions [#usage-restrictions] The Direct Travel API serves live pricing and availability for consumer apps, MCP servers, and similar integrations. You may **not** hard- or cold-store listings in your own database, and you may **not** use it for data analysis. It is intentionally RESTful, with limited information per listing, for instant consumption by your end users. Not sure you need the API? Start with [Allez](/docs/allez) or [Maps](/docs/maps) — a hosted link or widget covers most use cases without a backend. # Quickstart (/docs/api/quickstart) This guide makes one working request to the Direct Travel API — a live search for hotels in Paris — and reads the response. Swap in any address or coordinates once it works. ## Prerequisites [#prerequisites] * **API token** (optional to start) — create one in the [Hub](https://hub.stay22.com/en/settings/api). You can skip it and use demo mode (5 req/min) for the first call. ## Make a request [#make-a-request] To search, call `GET /v2/accommodations` with a location and your key in the `X-API-KEY` header. `provider` is optional — omit it to search all suppliers at once. ```bash curl -H "X-API-KEY: " \ "https://api.stay22.com/v2/accommodations?address=Paris" # get at hub.stay22.com — or omit the header for demo mode ``` ## Read the response [#read-the-response] Each stay arrives in `results[]`. Every stay carries a top-level `url` (a Stay22 deeplink that already carries your `aid`) and a `suppliers` map keyed by supplier. Without dates the search is **static** — no prices yet. ```json { "meta": { "page": 1, "pageSize": 10, "total": 29758, "hasMore": true, "currency": "USD" }, "results": [ { "id": "42f1014f...0000", "name": "Appartement aux portes de Paris", "type": "Apartment", "url": "https://www.stay22.com/allez/roam/...?aid=", "suppliers": { "booking": { "id": "15711178", "link": "https://www.stay22.com/allez/booking/15711178?aid=" } } } ], "_links": { "next": "https://api.stay22.com/v2/accommodations?address=Paris&page=2" } } ``` Each `suppliers.` entry has that supplier's `id`, a booking `link`, and (with dates) a `price`. Follow `_links.next` to page through results. See [Response & supplier model](/docs/api/concepts/response-model). ## Add dates for live prices [#add-dates-for-live-prices] Without dates, the search returns **static** results (no prices). Pass `checkin`/`checkout` to get a per-supplier `price` for each stay: ```bash curl -H "X-API-KEY: " \ "https://api.stay22.com/v2/accommodations?address=Paris&checkin=2026-07-01&checkout=2026-07-04" ``` Each supplier now carries a `price.total` (full-stay total in `meta.currency`), and `meta` echoes your window: ```json { "meta": { "page": 1, "pageSize": 10, "total": 18342, "hasMore": true, "currency": "USD", "nights": 3 }, "results": [ { "suppliers": { "booking": { "price": { "total": 252 } } } } ] } ``` See [Pricing & quotes](/docs/api/concepts/pricing). ## What to read next [#what-to-read-next] * [Search Accommodation](/docs/api/accommodations/search) — every parameter, with a live playground * [Concepts](/docs/api/concepts) — locations, pricing, caching, pagination, and clustering * [Code Examples](/docs/api/recipes/code-examples) — JavaScript, Python, and cURL # Rate Limits (/docs/api/rate-limits) The Direct Travel API rate-limits each API key (or each IP in demo mode) on a 60-second sliding window. Requests over the limit return `429` with code `RATE_LIMIT_EXCEEDED`. ## Tiers [#tiers] | Tier | Rate Limit | Tracking | Description | | ------------ | ----------- | ---------- | ------------------- | | **Demo** | 5 req/min | By IP | No API key required | | **Standard** | 100 req/min | By API key | With valid API key | | **Custom** | Contact us | By API key | Enterprise needs | Rate limits use a **sliding window** (60 seconds), not fixed intervals. ## Response headers [#response-headers] Every response carries your current rate-limit state, so you can throttle before you hit `429`: | Header | Meaning | | ----------------------- | --------------------------------------- | | `X-RateLimit-Limit` | Requests allowed in the current window | | `X-RateLimit-Remaining` | Requests left in the current window | | `X-RateLimit-Reset` | When the window resets, in milliseconds | When a request is rejected, retry with **exponential backoff**. For error formats, refer to the [API Reference](/docs/api) — error responses (4xx/5xx) are documented under the endpoint definition. ## Exemptions [#exemptions] These endpoints are not rate-limited: * Static resources * Documentation pages ## FAQ [#faq] **Do failed requests count?** Yes. Rate limiting runs before request processing, so every request counts regardless of response status. **Do cached responses count?** Yes. Rate limiting runs as a `preHandler` hook before the cache is checked. Your own client-side cache does not count. **Can I have multiple API keys?** Yes. Each key has its own rate limit. Useful for separating dev/staging/production. **Need higher limits?** Contact your assigned account manager or reach out via the [Hub](https://hub.stay22.com/) chat with your expected volume and use case. # Overview (/docs/maps) Drop an iframe on your page, point it at a location, and your users see an interactive map with nearby hotels and rentals - with your affiliate tracking built in. ## How it works [#how-it-works] You build an iframe URL with your affiliate ID and a destination You put that iframe in your HTML Users see an interactive map with accommodation markers near the destination Clicking a listing redirects through Allez - generating affiliate commissions No API dependency, no backend work. This is Stay22's first product and [how Stay22 got its start](https://www.stay22.com/about-us). ## Quick example [#quick-example] ```html ``` That's a working map embed. The `aid` tracks revenue to you, the `address` centers the map. Everything else is optional. ## When to use Maps [#when-to-use-maps] * You want a visual accommodation experience (map with markers, not just links) * Your content is location-based (events, venues, travel guides, city pages) * You want a drop-in widget with no backend work * You're deploying at scale - build iframe URLs dynamically from your page data ## Next steps [#next-steps] * [Quick Start](/docs/maps/quick-start) - Full setup in 2 minutes * [Parameters](/docs/maps/parameters) - All iframe URL parameters * [Widescale](/docs/maps/widescale) - Deploy maps dynamically at scale # Parameters (/docs/maps/parameters) All parameters are passed as query strings in the iframe `src` URL: ``` https://www.stay22.com/embed/gm?aid=&address=Bell+Centre+Montreal&checkin=2026-08-15 ``` ## Try it [#try-it] Toggle parameters on and off to see how they affect the map in real time. ## Required [#required] ## Location [#location] At least one location parameter is needed. Coordinates are the most precise. ## Dates [#dates] ## Guests [#guests] ## Tracking [#tracking] ## Viewport & Bounds [#viewport--bounds] Control the map viewport, zoom level, and visible area. ## GPX Trails [#gpx-trails] Render a route or trail on the map from a GPX file. ## Filters [#filters] ## Visual [#visual] These change how the map looks. Toggle them in the playground above to see what each one does. ## Colors [#colors] All hex values, without the `#` prefix. ## Custom Content [#custom-content] ## Provider Selection [#provider-selection] ## Listing Filtering [#listing-filtering] Show, hide, or feature specific listings by their provider IDs. All ID lists are comma-separated. ## UI Toggles [#ui-toggles] Hide specific UI elements. All are boolean — set to `true` to hide. ## Localization [#localization] ## Full example [#full-example] ```html ``` ## Sizing [#sizing] The iframe is responsive by default. Recommended dimensions: * **Width:** `100%` of the container * **Height:** `380px` is a good default; adjust to fit your layout * **Border:** `none` for a clean look ```css iframe { width: 100%; height: 380px; border: none; border-radius: 8px; /* optional */ } ``` ## See also [#see-also] * [Quick Start](/docs/maps/quick-start) - Get started in 2 minutes * [Widescale](/docs/maps/widescale) - Deploy maps at scale * [Secrets](/docs/maps/secrets) - Advanced script-based features # Quick Start (/docs/maps/quick-start) If you're looking to build a one-off map for a specific event, simply use the [map builder in your hub](https://hub.stay22.com/en/maps). This page is for you to know how to generate these programmatically and at scale. ## The minimum viable embed [#the-minimum-viable-embed] You need two things: your affiliate ID and a destination. ```html ``` Remember to replace the placeholder aid from this code. It's more common than you think. That's it. Drop it in your HTML and you have an interactive accommodation map. ## Using coordinates [#using-coordinates] If you have lat/lng, use them - they're more precise and skip the geocoding step: ```html ``` ## Adding dates [#adding-dates] If you're building a map around a specific event, then pre-filling the check in and check out dates will show live pricing and availability on the map's pins. ```html ``` ## Adding tracking [#adding-tracking] Use `campaign` to distinguish where your bookings come from: ```html ``` ## Building the URL dynamically [#building-the-url-dynamically] Here is a simple javascript snippet to show you (or your LLM) how to build a map on your page with `URLSearchParams`: ```javascript function buildMapSrc({ aid, address, lat, lng, checkin, checkout, campaign }) { const params = new URLSearchParams({ aid }); if (lat && lng) { params.set('lat', lat); params.set('lng', lng); } else if (address) { params.set('address', address); } if (checkin) params.set('checkin', checkin); if (checkout) params.set('checkout', checkout); if (campaign) params.set('campaign', campaign); return `https://www.stay22.com/embed/gm?${params}`; } ``` ## Next steps [#next-steps] * [Parameters](/docs/maps/parameters) - All available URL parameters * [Widescale](/docs/maps/widescale) - Deploy maps dynamically at scale * [Secrets](/docs/maps/secrets) # Secrets (/docs/maps/secrets) If you're reading this, we feel you can enjoy trying these out. Fair warning: if you reach out to our support chat, they will likely have no idea what you're talking about. You're on your own here. These are advanced integration features built on top of the Maps iframe using our SMAPS script. They add display modes, button triggers, multi-instance deployments, and more. The iframe approach from the main docs is the recommended path - this is for people who want more control. ## The SMAPS script [#the-smaps-script] Instead of building the iframe yourself, you can let the SMAPS script handle it: ```html
``` The script reads your config and generates an iframe automatically. ## Display modes [#display-modes] The script supports four display modes via `displaySettings.display`: ### `onload_div` - Inline embed [#onload_div---inline-embed] Map renders directly into the page when it loads. ```javascript displaySettings: { display: 'onload_div', a22mapQS: '#hotel-map' } ``` ### `onload_popmodal` - Modal on page load [#onload_popmodal---modal-on-page-load] Map opens as a modal overlay when the page loads. Use sparingly. ```javascript displaySettings: { display: 'onload_popmodal' } ``` ### `onclick_slidedown` - Button + slide reveal [#onclick_slidedown---button--slide-reveal] A button appears; clicking it slides the map down. ```javascript displaySettings: { display: 'onclick_slidedown', a22mapQS: '#hotel-map', btnText: 'View Hotels Nearby', mainColor: '0069ed' } ``` ### `onclick_popmodal` - Button + modal [#onclick_popmodal---button--modal] A button appears; clicking it opens the map in a modal. ```javascript displaySettings: { display: 'onclick_popmodal', a22mapQS: '#hotel-map', btnText: 'Find Accommodation', mainColor: '0069ed' } ``` ### Choosing a mode [#choosing-a-mode] | Mode | Use when... | | ------------------- | ------------------------------------------------------ | | `onload_div` | The map is a core part of the page | | `onload_popmodal` | You want immediate visibility but the map isn't inline | | `onclick_slidedown` | The map is secondary - let users opt in | | `onclick_popmodal` | Same as slidedown, but you prefer a modal | ## Configuration reference [#configuration-reference] ### `mapParams` [#mapparams] ### `displaySettings` [#displaysettings] ### Insertion points [#insertion-points] Instead of `a22mapQS`, you can use explicit insertion positions: ## Multi-instance with `parentBlock` [#multi-instance-with-parentblock] The key feature for deploying maps at scale. It tells the script: "find every element matching this selector, and create a map for each one." ```html

Concert at Bell Centre

Festival at Place des Arts

``` ```javascript window.Stay22 = { mapParams: { aid: '', address: 'h3' }, displaySettings: { display: 'onclick_slidedown', parentBlock: '.event-card', a22mapQS: '.map-slot', btnText: 'View Hotels' } }; ``` The script finds every `.event-card`, reads the `

` text as the address, and creates a map in each `.map-slot`. ## URL-specific overrides [#url-specific-overrides] Both `mapParams` and `displaySettings` support `urlSpecs` for per-page overrides: ```javascript mapParams: { aid: '', address: 'Default City', urlSpecs: { 'example.com/festivals': { address: 'Parc Jean-Drapeau, Montreal' } } } ``` ## Styling [#styling] ### `mainColor` [#maincolor] Sets the primary color for buttons and UI accents. Hex value **without** the `#` prefix: ```javascript mainColor: 'ff5a5f' // Airbnb-ish red mainColor: '0069ed' // Stay22 blue (default) ``` ### Custom CSS classes [#custom-css-classes] ```javascript displaySettings: { btnClass: 'btn btn-primary btn-lg', mapClass: 'my-map-container' } ``` ### Map dimensions [#map-dimensions] Default: width 100%, max height 380px. Override with CSS: ```css .s22-responsive-wrap { max-height: 500px; } .s22-responsive-wrap iframe { height: 500px; } ``` ### Modal z-index [#modal-z-index] If the modal appears behind sticky headers: ```css .s22-frametoggle { z-index: 10000; } ``` ## Schema.org JSON-LD auto-detection [#schemaorg-json-ld-auto-detection] The script auto-detects event details from `application/ld+json` markup: ```html ``` If JSON-LD data is present and no `address`/`lat`/`lng` is set, the script uses it automatically. ## Need help? [#need-help] Actually, remember what we said at the top? You're on your own here. But if you're truly stuck, try [slava@stay22.com](mailto:slava@stay22.com) - he might know what you're talking about. # Troubleshooting (/docs/maps/troubleshooting) Common issues with Maps embeds and how to fix them. ## Map shows wrong location [#map-shows-wrong-location] * **Address typo:** Double-check the `address` value. City + country is the minimum for reliable results. * **Coordinates reversed:** `lat` is north/south, `lng` is east/west. Common mistake: passing them backwards. * **URL encoding:** Encode spaces as `+` or `%20` in the iframe src. ## Map iframe too tall / too short [#map-iframe-too-tall--too-short] Set explicit dimensions on the iframe: ```html ``` ## Map not loading [#map-not-loading] * **Missing `aid`:** The `aid` parameter is required. Get one at [hub.stay22.com](https://hub.stay22.com). * **CSP / iframe blocking:** If your site has a Content Security Policy, make sure `stay22.com` is allowed in `frame-src`. * **Ad blockers:** Some ad blockers may block the embed. Not much you can do about this, but it only affects a small percentage of users. ## Dates not showing [#dates-not-showing] * Format must be `YYYY-MM-DD` (e.g., `2026-08-15`) * `eventstart`/`eventend` are SMAPS-script parameters for event dates (separate from the `checkin`/`checkout` stay dates) — see [Secrets](/docs/maps/secrets). ## Need help? [#need-help] Reach out to [support@stay22.com](mailto:support@stay22.com) or use the chat in [hub.stay22.com](https://hub.stay22.com). # Widescale (/docs/maps/widescale) Widescale is the pattern for automatic link and map deployment at scale. Instead of manually creating each Allez link or Map embed, you point at the data already on your page and let it build itself. This is how partners with thousands of listings - event sites, travel directories, city guides - integrate without maintaining individual URLs. ## See it in action [#see-it-in-action] The playground below traces how a single event card's data becomes a working affiliate link. Edit the dates or campaign to watch the URL rebuild itself. ## How it works [#how-it-works] Your page already has the data: a venue name, coordinates, event dates. Widescale just means reading those values and building Allez URLs from them programmatically. ### JavaScript: build links from DOM elements [#javascript-build-links-from-dom-elements] ```html

Concert at Bell Centre

Aug 15, 2026

Find Hotels
``` ```javascript const AID = ''; document.querySelectorAll('.event-card').forEach(card => { const lat = card.dataset.lat; const lng = card.dataset.lng; const checkin = card.querySelector('.date')?.dataset.checkin; const checkout = card.querySelector('.date')?.dataset.checkout; const params = new URLSearchParams({ aid: AID }); if (lat && lng) { params.set('lat', lat); params.set('lng', lng); } if (checkin) params.set('checkin', checkin); if (checkout) params.set('checkout', checkout); params.set('campaign', 'events_page'); const link = card.querySelector('.hotel-link'); if (link) { link.href = `https://www.stay22.com/allez/roam?${params}`; link.target = '_blank'; link.rel = 'noopener'; } }); ``` ### React / Next.js [#react--nextjs] ```javascript function EventCard({ event }) { const allezUrl = new URL('https://www.stay22.com/allez/roam'); allezUrl.searchParams.set('aid', process.env.NEXT_PUBLIC_STAY22_AID); allezUrl.searchParams.set('lat', event.venue.lat); allezUrl.searchParams.set('lng', event.venue.lng); allezUrl.searchParams.set('checkin', event.startDate); allezUrl.searchParams.set('checkout', event.endDate); allezUrl.searchParams.set('campaign', `event_${event.id}`); return ( ); } ``` ### Python (server-side) [#python-server-side] ```python from urllib.parse import urlencode def build_allez_links(events, aid): links = [] for event in events: params = { "aid": aid, "lat": event["lat"], "lng": event["lng"], "checkin": event["start_date"], "checkout": event["end_date"], "campaign": f"event_{event['id']}", } links.append(f"https://www.stay22.com/allez/roam?{urlencode(params)}") return links ``` ## Widescale for Maps [#widescale-for-maps] The same pattern works for Maps embeds. Instead of building Allez links, construct iframe `src` URLs: ```javascript document.querySelectorAll('.event-card').forEach(card => { const lat = card.dataset.lat; const lng = card.dataset.lng; const slot = card.querySelector('.map-slot'); if (lat && lng && slot) { const params = new URLSearchParams({ aid: '', lat, lng, campaign: 'events_map', }); const iframe = document.createElement('iframe'); iframe.src = `https://www.stay22.com/embed/gm?${params}`; iframe.style.cssText = 'width:100%;height:380px;border:none;'; iframe.loading = 'lazy'; slot.appendChild(iframe); } }); ``` ## Best practices [#best-practices] 1. **Use coordinates** (`lat`/`lng`) when you have them - more precise, skips geocoding 2. **Set a campaign per page or section** - makes it easy to track which pages generate bookings 3. **Use `/allez/roam`** - let the AI pick the best provider instead of hard-coding one 4. **Include dates** when you have them - pre-filled dates dramatically improve conversion 5. **URL-encode values** - especially addresses with special characters ## See also [#see-also] * [Allez Quick Start](/docs/allez/quick-start) - Single-link basics * [Maps Quick Start](/docs/maps/quick-start) - Single-embed basics * [Providers](/docs/allez/providers) - Provider details and geo-routing # Overview (/docs/mobile-sdk) Your users already tell your app where they're going — they book a flight, save an event, search a destination. The Stay22 Mobile SDK turns that travel intent into a new revenue stream: a single, well-timed local notification that opens a curated accommodation booking experience, with every booking attributed to you. ## How it works [#how-it-works] A traveler drops signals in your app — they search a destination, pick travel dates, save a place You send those signals to the SDK — destination, dates, places, whatever you have — with a single call: `Stay22.setTravelContext(...)` The SDK watches for exactly the right moment, then shows **one** local notification for that trip One tap drops the traveler into a curated Stay22 booking experience for their destination and dates Every booking is attributed to your partner ID (`aid`) and earns you affiliate revenue There's no backend work on your side: you install the SDK, tell it about the trip, and Stay22 handles timing, relevance, and the booking experience. ## One notification, well timed [#one-notification-well-timed] The SDK is deliberately conservative — a spammy notification costs you user trust, so it only shows one when it's worth showing: * **One notification per trip.** Setting a new travel context replaces any pending notification; it never stacks. * **Timed for relevance.** Delivery waits until the user has left your app, after a delay tuned by Stay22. * **Local suppression.** If the destination looks like home — the user's own city, or anywhere within a configurable suppression range — no notification is shown. * **Frequency capping.** A cooldown prevents users from being notified too often, even across multiple trips. * **Fully configurable.** Everything the user sees is yours to shape — title, message, grouping, badges, action buttons — with `{destination}` and date placeholders filled in per trip. See [Notifications & Events](/docs/mobile-sdk/notifications). Notification timing, delays, frequency caps, and the local suppression range are managed by Stay22 per partner — you don't configure them in code. Reach out to your Stay22 account manager or [support@stay22.com](mailto:support@stay22.com) to tune them for your account. ## Built around user consent [#built-around-user-consent] * **Opt-in by design.** Nothing happens until your app sets `Stay22.isEnabled = true` — gate it behind your own consent flow, and users can opt out at any time. * **No GPS.** The SDK never requests location permission and doesn't track users; it only acts on the travel context your app explicitly provides. * **Standard notification permission.** Notifications go through the OS permission prompt like any other — the SDK helps you request it and respects a denial. * **Privacy-manifest ready.** The iOS SDK ships an Apple privacy manifest and has no third-party dependencies. ## Requirements [#requirements] | Platform | Minimum version | Language | Distribution | | -------- | -------------------- | ---------- | ------------------------ | | Android | Android 8.0 (API 26) | Kotlin | Maven — `com.stay22:sdk` | | iOS | iOS 15.0 | Swift 5.9+ | Swift Package Manager | Both SDKs are distributed as prebuilt binaries through public GitHub repositories: Android SDK on GitHub — README, releases, and integration guide. iOS SDK on GitHub — README, releases, and integration guide. ## Next steps [#next-steps] * [Quick Start](/docs/mobile-sdk/quick-start) — install, initialize, and send your first travel context * [Notifications & Events](/docs/mobile-sdk/notifications) — customize the notification and observe the SDK's decisions * [Testing & Troubleshooting](/docs/mobile-sdk/testing) — verify your integration in minutes # Notifications & Events (/docs/mobile-sdk/notifications) ## The notification lifecycle [#the-notification-lifecycle] Once you set a travel context, the notification moves through a simple lifecycle — and the SDK tells you about every step through [events](#events): 1. **Scheduled.** The SDK accepts the trip and queues one notification for it. A newer travel context replaces the pending one. 2. **Delivered.** After the user leaves your app and the Stay22-managed delay elapses, the notification is shown. 3. **Opened.** The user taps it and lands in a curated booking experience for their destination and dates, attributed to your `aid`. 4. **Skipped or cancelled.** If conditions aren't right — the user is still in the app, the destination is their home city, a cooldown is active, permission is missing — the SDK holds back and emits the reason instead. The delivery delay and frequency caps are configured by Stay22 for your account, not in code — that keeps timing tuned without app releases. Contact your Stay22 account manager or [support@stay22.com](mailto:support@stay22.com) to adjust them. ## Customize the notification [#customize-the-notification] The default copy works out of the box, but you can match it to your app's voice with `notificationConfig`. Set it once, any time after initialization: ```kotlin import com.stay22.sdk.NotificationConfig import com.stay22.sdk.NotificationInterruptionLevel Stay22.notificationConfig = NotificationConfig( title = "Hotels in {destination}", subtitle = "{checkin} - {checkout}", message = "Tap to find the best deals nearby", categoryId = "stay22_destinations", threadId = "stay22", interruptionLevel = NotificationInterruptionLevel.active ) ``` ```swift Stay22.notificationConfig = NotificationConfig( title: "Hotels in {destination}", subtitle: "{checkin}-{checkout}", message: "Find stays near {destination}.", categoryId: "stay22_destinations", threadId: "stay22", badge: 1, interruptionLevel: .active, relevanceScore: 0.8 ) ``` iOS additionally supports `launchImageName`, `targetContentIdentifier`, image `attachments` (local files), and custom `actions` buttons — see the [iOS README](https://github.com/Stay22/stay22-ios-sdk) for the full shape. ### Text placeholders [#text-placeholders] Title, subtitle, and message support placeholders that the SDK fills in from the travel context: | Placeholder | Replaced with | | --------------- | ----------------------------- | | `{destination}` | The trip's destination name | | `{checkin}` | Check-in date, when provided | | `{checkout}` | Check-out date, when provided | ## Attribution [#attribution] Bookings are attributed to the `aid` you initialized with. To segment where conversions come from — much like `campaign` on [Allez links](/docs/allez/quick-start#add-tracking) — set an optional campaign ID: ```kotlin Stay22.campaignId = "spring_push" ``` ```swift Stay22.campaignId = "spring_push" ``` ## Events [#events] The SDK emits an event for every decision it makes, which is the best window into what it's doing — wire them into your analytics or logging: ```kotlin import com.stay22.sdk.Stay22Event Stay22.advanced.setEventHandler { event -> when (event) { is Stay22Event.NotificationScheduled -> log("scheduled: ${event.destination}") is Stay22Event.NotificationClicked -> log("clicked: ${event.destination}") is Stay22Event.NotificationSkipped -> log("skipped: ${event.reason}") else -> Unit } } ``` ```swift Stay22.advanced.setEventHandler { event in switch event { case .notificationScheduled(let destination, let delay): print("scheduled: \(destination), delay=\(delay)s") case .notificationClicked(let destination, let url): print("clicked: \(destination), \(url)") case .notificationSkipped(let reason): print("skipped: \(reason)") default: break } } ``` | Event | Fires when | | ----------------------- | ---------------------------------------------- | | `locationUpdated` | A travel context was accepted | | `notificationScheduled` | A notification was queued (destination, delay) | | `notificationShown` | The notification was delivered | | `notificationClicked` | The user tapped it (destination, booking URL) | | `notificationSkipped` | Scheduling was skipped (reason) | | `notificationBlocked` | Delivery was blocked (reason) | | `notificationCancelled` | A pending notification was cancelled (reason) | | `enabledChanged` | `Stay22.isEnabled` was toggled | | `travelContextCleared` | The travel context was cleared | ### Skip and block reasons [#skip-and-block-reasons] When the SDK holds back, the reason tells you why — most are expected behavior, not errors: | Reason | Meaning | | -------------------------------- | ----------------------------------------------------- | | `sdk_disabled` | `Stay22.isEnabled` is `false` | | `missing_destination` | The travel context has no usable destination | | `app_foreground` | The user was still in your app at delivery time | | `home_destination` | The destination looks like the user's home area | | `partner_config_disabled` | Scheduling is currently disabled for your account | | `local_cooldown` | A notification was shown too recently (frequency cap) | | `duplicate_pending_notification` | A notification for this trip is already pending | | `notification_permission_denied` | The user hasn't granted notification permission | ## Manual scheduling [#manual-scheduling] By default the SDK schedules automatically when a travel context is set. If you want to control the trigger yourself — for example, only after checkout of a flight purchase — run the same pipeline manually: ```kotlin Stay22.advanced.scheduleNotification() ``` ```swift Stay22.advanced.scheduleNotification() ``` All the same relevance checks still apply — manual scheduling triggers the pipeline, it doesn't bypass it. # Quick Start (/docs/mobile-sdk/quick-start) This guide takes you from an empty project to a working integration: the SDK installed, the user opted in, and a trip handed to Stay22. You need your partner ID (`aid`) — the same one you use for [Allez](/docs/allez) links. ## Install the SDK [#install-the-sdk] Add the Stay22 Maven repository in `settings.gradle.kts`: ```kotlin dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://raw.githubusercontent.com/Stay22/stay22-android-sdk/main/maven") } } } ``` Then add the dependency: ```kotlin dependencies { implementation("com.stay22:sdk:1.0.11") } ``` In Xcode, go to **File > Add Package Dependencies** and enter: ```text https://github.com/Stay22/stay22-ios-sdk ``` Or add it to `Package.swift`: ```swift dependencies: [ .package(url: "https://github.com/Stay22/stay22-ios-sdk.git", from: "1.0.10") ] ``` Add the `Stay22SDK` product to your app target. Check the [Android releases](https://github.com/Stay22/stay22-android-sdk/releases) and [iOS releases](https://github.com/Stay22/stay22-ios-sdk/releases) for the latest version. Both repos also offer manual distribution (a raw `.aar` / `.xcframework`) — see their READMEs. ## Initialize at app launch [#initialize-at-app-launch] Initialize once, as early as possible, with your partner ID. Set `Stay22.isEnabled` **before** initializing if you gate Stay22 offers behind your own consent flow — nothing is scheduled while it's `false`. Call from `Application.onCreate()`: ```kotlin import android.app.Application import com.stay22.sdk.Stay22 class MyApp : Application() { override fun onCreate() { super.onCreate() Stay22.isEnabled = userHasOptedInToStay22Offers Stay22.initialize(application = this, aid = "your-partner-id") } } ``` Call early in your app launch: ```swift import Stay22SDK Stay22.isEnabled = userHasOptedInToStay22Offers Stay22.initialize(aid: "your-partner-id") ``` ## Ask for notification permission [#ask-for-notification-permission] The SDK delivers standard local notifications, so the user has to grant notification permission. Request it at a moment that makes sense in your UX — ideally right after the user opts in to travel offers: ```kotlin Stay22.requestNotificationPermission() ``` Handles the Android 13+ runtime permission; on older versions it's a no-op. Check the current state anytime with `Stay22.hasNotificationPermission()`. ```swift Task { await Stay22.requestNotificationPermission() } ``` Prompts for local notification permission. Check the current state anytime with `await Stay22.hasNotificationPermission()`. ## Tell the SDK about the trip [#tell-the-sdk-about-the-trip] This is the heart of the integration. Whenever your app learns about a trip — a booked flight, a searched destination, saved travel dates — pass it along. The SDK takes it from there: ```kotlin import com.stay22.sdk.TravelContext Stay22.setTravelContext( TravelContext( address = "Paris, France", checkinDate = "2026-03-20", checkoutDate = "2026-03-25" ) ) ``` ```swift Stay22.setTravelContext( TravelContext( address: "Paris, France", checkinDate: "2026-03-20", checkoutDate: "2026-03-25" ) ) ``` Every field is optional — a destination alone is enough to start. Dates use `YYYY-MM-DD`. You can also pass `latitude`/`longitude` for precision, or `hotelName` when the user searched a specific property. If the trip is cancelled or the user clears their search, call `Stay22.clearTravelContext()` to drop the context and any pending notification. ## See it work [#see-it-work] In a normal run the notification arrives after the user leaves your app, on Stay22's schedule — which is the right behavior in production but slow for a first test. Flip on [test mode](/docs/mobile-sdk/testing) to get the notification 5 seconds after backgrounding the app. ## Platform setup notes [#platform-setup-notes] The SDK's manifest merges in everything it needs (`INTERNET`, `POST_NOTIFICATIONS`, and the activity that handles notification taps) — no required manifest changes. Optionally, give the notification your own status-bar icon: ```xml ``` Add these entries to your `Info.plist` so the SDK can route the offer to a booking app the user already has installed: ```xml LSApplicationQueriesSchemes airbnb booking expda hotelsapp agoda ``` ## Next steps [#next-steps] * [Notifications & Events](/docs/mobile-sdk/notifications) — customize the notification copy and observe scheduling decisions * [Testing & Troubleshooting](/docs/mobile-sdk/testing) — verify the full flow on a device * The GitHub READMEs ([Android](https://github.com/Stay22/stay22-android-sdk), [iOS](https://github.com/Stay22/stay22-ios-sdk)) carry the full API reference and a step-by-step integration guide you can hand to an AI coding agent # Testing & Troubleshooting (/docs/mobile-sdk/testing) ## Test mode [#test-mode] In production the notification arrives on Stay22's schedule, after the user leaves your app — too slow for a development loop. The SDK ships QA flags that compress the whole flow into seconds: ```kotlin Stay22.testing.force = true Stay22.testing.showLogs = true Stay22.setTravelContext(TravelContext(address = "Paris, France")) ``` ```swift Stay22.testing.force = true Stay22.testing.showLogs = true Stay22.setTravelContext(TravelContext(address: "Paris, France")) ``` * `testing.force` — schedules with a fixed 5-second delay and relaxes the relevance checks, so you always get a notification. * `testing.showLogs` — prints the SDK's decisions to the console. To run a full test: enable both flags, set a travel context, background the app, and the notification appears about 5 seconds later. Tap it to verify the booking experience opens with your attribution. Never ship a build with `testing.force` or `testing.showLogs` enabled — force mode bypasses the timing and relevance rules that protect your users from notification spam. ## Why didn't the notification show? [#why-didnt-the-notification-show] The SDK reports every held-back notification as a `notificationSkipped`, `notificationBlocked`, or `notificationCancelled` event with a reason — set an [event handler](/docs/mobile-sdk/notifications#events) and check it first. The usual suspects: | Symptom | Check | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Nothing happens at all | Is `Stay22.isEnabled` set to `true`? Did `Stay22.initialize` run at app launch with your `aid`? | | `notification_permission_denied` | The user declined the OS prompt — call `Stay22.requestNotificationPermission()` at a better moment, or guide them to system settings. | | `app_foreground` | Notifications only deliver after the user leaves the app. Background the app and wait. | | `home_destination` | The test destination matches the device's home area — test with a faraway city. | | `local_cooldown` | A notification was shown recently. Use `testing.force` while developing, or wait out the cooldown. | | `missing_destination` | The travel context needs at least an address, coordinates, or hotel name. | | `partner_config_disabled` | Scheduling is switched off for your account — contact your Stay22 account manager or [support@stay22.com](mailto:support@stay22.com). | Also worth knowing while testing: * Setting a new travel context **replaces** the pending notification — you'll see a `notificationCancelled` followed by a fresh `notificationScheduled`. * `Stay22.clearTravelContext()` drops the context and any pending notification, which is a clean way to reset between test runs. ## Still stuck? [#still-stuck] Enable `testing.showLogs`, reproduce the issue, and send the console output along with your `aid` to [support@stay22.com](mailto:support@stay22.com). The GitHub READMEs ([Android](https://github.com/Stay22/stay22-android-sdk), [iOS](https://github.com/Stay22/stay22-ios-sdk)) also document the full API surface if you need details beyond these guides. # Search Accommodation (/docs/api/accommodations/search) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Agent Skills (/docs/api/build-with-ai/agent-skills) Install the accommodation search skill to give your AI coding agent access to live travel inventory from Booking.com, Vrbo, Expedia, and Hotels.com. ## Install [#install] One command to add the skill to your project: ```bash mkdir -p .claude/skills/accommodation-search && curl -sL https://api.stay22.com/.well-known/agent-skills/accommodation-search/SKILL.md -o .claude/skills/accommodation-search/SKILL.md ``` ```bash mkdir -p .agents/skills/accommodation-search && curl -sL https://api.stay22.com/.well-known/agent-skills/accommodation-search/SKILL.md -o .agents/skills/accommodation-search/SKILL.md ``` ```bash mkdir -p ~/.agents/skills/accommodation-search && curl -sL https://api.stay22.com/.well-known/agent-skills/accommodation-search/SKILL.md -o ~/.agents/skills/accommodation-search/SKILL.md ``` After installing, your agent will automatically use this skill when tasks involve finding hotels, comparing lodging, or checking availability. ## `accommodation-search` [#accommodation-search] Search live travel inventory for hotels and vacation rentals with real-time availability and pricing. Connects to the Direct Travel API via [MCP](/docs/api/build-with-ai/mcp) or [REST](/docs/api/recipes/code-examples). ````markdown --- name: accommodation-search description: Search live travel inventory for hotels and vacation rentals. Use when a task involves finding accommodation, comparing lodging options, or checking hotel availability and pricing. --- # Accommodation Search Search across multiple travel providers (Booking.com, Vrbo, Expedia, Hotels.com) for hotels and vacation rentals with real-time availability and pricing. ## Connecting via MCP The recommended way for agents to use this skill is through the MCP server. **Endpoint:** `https://api.stay22.com/mcp/v2` ### Configuration ```json { "mcpServers": { "stay22-travel": { "url": "https://api.stay22.com/mcp/v2?key=YOUR_API_KEY" } } } ``` An API key is optional for demo access (limited to 5 requests/minute). Get a key at https://api.stay22.com/docs/ for higher limits. ## Tool: search_accommodations Search for accommodations by location with filters for dates, guests, price, and ratings. ### Location (one required) | Parameter | Type | Description | |-----------|------|-------------| | `address` | string | Place name or address (e.g., "Eiffel Tower", "Paris, France") | | `lat` + `lng` | number | Center point coordinates. Optional `radius` in meters (default 10000) | | `nelat` + `nelng` + `swlat` + `swlng` | number | Bounding box corners for map-based search | | `hotelids` | string | Comma-separated accommodation IDs for direct lookup | ### Filters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `provider` | string | — | Optional. One or more of `booking`, `vrbo`, `expedia`, `hotelscom` (CSV, e.g. `booking,vrbo`); omit to search all suppliers | | `checkin` | string | — | Check-in date (YYYY-MM-DD); omit for a static (no-price) search | | `checkout` | string | — | Check-out date (YYYY-MM-DD); omit for a static (no-price) search | | `adults` | number | 2 | Number of adults | | `children` | number | 0 | Number of children | | `rooms` | number | 1 | Number of rooms | | `type` | string | both | `hotel` or `rental` | | `min` | number | 0 | Min per-night price (USD) | | `max` | number | 1000 | Max per-night price (USD) | | `currency` | string | USD | Display currency (ISO 4217) | | `minstarrating` | number | — | Min hotel star rating (0-5) | | `minguestrating` | number | — | Min guest review rating (0-10) | | `minratingcount` | number | — | Min number of guest reviews | | `pageSize` | number | 10 | Results per page (1-100) | | `page` | number | 1 | 1-indexed page number | | `lang` | string | en | Language code (ISO 639-1) | ### Response The response wraps `results[]` with `meta` and `_links`: - **`results[]`** — each stay carries: - **id**, **name**, **type** (hotel, apartment, villa, etc.) - **location** (address, coordinates, distance from search center), **rating** (guest 0-10, stars 1-5, review count), **capacity**, **policies**, **media** - **url** — provider-agnostic deeplink (resolves to the best supplier at click time) - **suppliers** — map keyed by supplier (`booking`, `vrbo`, `expedia`, `hotelscom`); each entry has the supplier-specific **id**, a booking **link**, brand **media**, and **price.total** (full-stay total in the response currency) — present only when `checkin`/`checkout` were supplied - **`meta`** — `total`, `count`, `page`, `pageSize`, `hasMore`, `currency`, `nights` - **`_links`** — `next` for pagination (follow until absent) ### Example prompts - "Find hotels near the Eiffel Tower for 2 adults, checking in March 15" - "Search for vacation rentals in Barcelona under $150/night" - "Compare Booking.com hotels near Times Square with at least 4 stars" ## REST API fallback If MCP is unavailable, the same data is accessible via HTTP: ``` GET https://api.stay22.com/v2/accommodations?address=Paris ``` Pass `x-api-key` header or `key` query parameter for authentication. Full OpenAPI spec at https://api.stay22.com/openapi.json. ## Rate limits | Tier | Limit | |------|-------| | Demo (no key) | 5 requests/minute | | Authenticated | 100 requests/minute | ```` ### Related [#related] * [MCP Integration](/docs/api/build-with-ai/mcp) — connect your AI assistant directly via MCP * [Authentication](/docs/api/authentication) — get an API key for higher rate limits * [Code Examples](/docs/api/recipes/code-examples) — REST API usage examples # Overview (/docs/api/build-with-ai) The Direct Travel API is built for AI agents as first-class consumers — query live travel inventory over MCP or equip an agent with a ready-made skill. ## What to read next [#what-to-read-next] * [MCP Integration](/docs/api/build-with-ai/mcp) — connect Claude, Cursor, and other MCP clients * [Agent Skills](/docs/api/build-with-ai/agent-skills) — equip an AI agent with Stay22 travel search ## For coding agents [#for-coding-agents] The whole docs surface is machine-readable: [`/llms.txt`](/llms.txt) indexes every page, [`/llms-full.txt`](/llms-full.txt) is the full corpus, and any page is available as raw Markdown by appending `.md` to its URL (e.g. `/docs/api/quickstart.md`). # MCP Integration (/docs/api/build-with-ai/mcp) The Direct Travel API speaks the **Model Context Protocol (MCP)** — an open standard that lets AI assistants (like Claude, Cursor, or Windsurf) call the API as a "tool." Instead of writing code, you describe what you want in plain English and the assistant translates that into an API call. With the MCP server, you can ask: > "Find me hotels near the Eiffel Tower under $200 per night" ...and the assistant calls the Direct Travel API, gets live results, and presents them — no code required. Learn more about MCP at [modelcontextprotocol.io](https://modelcontextprotocol.io). ## Prerequisites [#prerequisites] 1. An API key from [hub.stay22.com](https://hub.stay22.com/) (see [Authentication](/docs/api/authentication)) 2. An MCP-compatible client (see setup below) ## Setup [#setup] To connect an AI assistant to the Direct Travel API, provide it with the URL to the MCP server. Most MCP clients use a JSON configuration file where you define your connected servers. The server URL is: ``` https://api.stay22.com/mcp/v2?key= ``` Replace `` with your actual key (see [Authentication](/docs/api/authentication#get-an-api-key)). See [Server versions](#server-versions) for the difference between `/mcp/v2` and the deprecated `/mcp`. ### Configuration Example [#configuration-example] Add the server to your MCP client's configuration (often named `claude_desktop_config.json`, `.mcp.json`, or configured through the UI in clients like Cursor or Windsurf): ```json { "mcpServers": { "stay22": { "url": "https://api.stay22.com/mcp/v2?key=", "transport": "http" } } } ``` *Note: Depending on your client, the transport type might need to be set to `"sse"` or `"http"` (the server uses an HTTP streamable transport). Refer to your client's documentation for exact configuration details.* ## Server versions [#server-versions] * **`/mcp/v2`** (current) — backed by the unified search; one `search_accommodations` call returns all suppliers per stay. Use this for new integrations. * **`/mcp`** (deprecated) — the legacy single-provider MCP server. Kept for backward compatibility; prefer `/mcp/v2`. ## Available Tool [#available-tool] ### `search_accommodations` [#search_accommodations] Searches accommodations across the supported providers (`booking`, `expedia`, `vrbo`, and `hotelscom`). This tool uses the exact same parameters and functionality as the standard REST endpoints. For a full list of available inputs (like location, dates, guests, and filters), see the **[API Reference](/docs/api)**. ## Example Prompts [#example-prompts] Once connected, you can simply interact with your AI assistant in natural language. Here are a few examples of what you can ask: * "Find hotels in Paris" * "Find VRBO rentals in NYC for a family, under $300 per night, with an 8+ rating" * "Show me hotels within 2km of the Eiffel Tower" * "Compare prices for a 4-star hotel in Tokyo across Booking and Expedia" ## Rate Limits [#rate-limits] MCP calls use the same API key as REST calls, so standard [rate limits](/docs/api/rate-limits) apply (5 req/min demo, 100 req/min with API key). ## Tips [#tips] * **Be specific with location** — "hotels near the Louvre in central Paris" works better than just "hotels" * **Points of interest work** — "Eiffel Tower", "Berliner Dom", "Times Square" are all valid locations * **Include dates when relevant** — omit them for a static (no-price) search * **Try different providers** — each provider has different inventory and pricing * **Ask for comparisons** — the AI can call the tool multiple times and compare results ## Troubleshooting [#troubleshooting] **The AI doesn't see the tool** * Double-check your config JSON syntax (missing commas, wrong quotes) * Verify your API key is valid — test it with a [cURL request](/docs/api/recipes/code-examples) first * Restart your AI client after changing the config **"Rate limit exceeded" errors** * You've exceeded 100 requests/minute (or 5/minute in demo mode) * Wait 60 seconds and try again * [Get an API key](/docs/api/authentication#get-an-api-key) if you're in demo mode **No results returned** * Try a broader location (city name instead of specific address) * Remove strict filters (high `minguestrating`, low `max` price) * Try a different provider — not all providers have inventory everywhere **"Invalid API key" error** * Check that your key is correctly pasted in the config URL * Make sure there are no extra spaces or line breaks in the key * Generate a new key by following the [Authentication](/docs/api/authentication#get-an-api-key) guide # Caching (/docs/api/concepts/caching) Cache search responses in a KV store for up to 60 minutes. Prices and availability change, so 60 minutes is the ceiling — but caching even for a few minutes guards against rate limits and burst traffic. ## The `X-Cache` header [#the-x-cache-header] Every response carries `X-Cache: HIT` or `X-Cache: MISS`, so you can see whether a result came from the Direct Travel API's own cache. Caching is for live, short-lived consumption. You may **not** hard- or cold-store listings in your own database, and you may **not** use the API for data analysis. See the [Overview](/docs/api) for usage restrictions. ## What to read next [#what-to-read-next] * [Pricing & quotes](/docs/api/concepts/pricing) — why prices are short-lived * [Rate Limits](/docs/api/rate-limits) — what caching helps you stay within * [Pagination](/docs/api/concepts/pagination) — caching paged requests # Clustering (/docs/api/concepts/clustering) For dense map viewports, the search can summarize stays into hex-grid (H3) markers instead of returning a flat list. Clustering is off by default. ## Modes [#modes] Set the `cluster` parameter: | `cluster` | Behavior | Use case | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | `false` (or omitted) | Flat list of stays | Default; lists and simple grids | | `auto` | Server clusters dense viewports into H3 markers when it judges clustering useful | Map UIs that want automatic behavior | | `true` | Forces H3 clustering whenever geometrically possible; on tight viewports clamps to the deepest resolution (r8, \~460 m hexes) | Map UIs that always want markers | | `top` | Returns ONE representative stay (the best-rated) per H3 cell in `results[]` (each tagged with `cellId`), paginated over cells. Unlike `auto`/`true`, it keeps clustering at tight zoom instead of falling back to a flat list | A lightweight "top stays per area" overview | `precision` optionally overrides the H3 cell size for `cluster=top`; it is ignored (not validated) for other modes. Range `4` (\~22 km hexes) to `8` (\~460 m hexes). When omitted, the server derives a sensible resolution from the viewport (one step coarser than count-clustering); an explicit value also forces top mode on tight viewports that would otherwise return a flat list. `meta.clustering.mode` reports which mode ran (for example, `top` for a `cluster=top` request). ## Clustered responses [#clustered-responses] `auto`/`true` responses include a separate `clusters[]` array; sparse stays continue to flow through `results[]`, and a stay never appears in both. Each cluster includes `count`, `location.bounds`, `location.coordinates`, and a pre-built `_links.expand` URL for drilling into that cluster's area. Structural blockers — ID lookups, center+radius searches without a bounding box, and requests without a resolvable viewport — always return a flat list even with `cluster=true` or `cluster=top`. ## What to read next [#what-to-read-next] * [Pagination](/docs/api/concepts/pagination) — paging clustered results and `meta.total` * [Response & supplier model](/docs/api/concepts/response-model) — `clusters[]` vs `results[]` * [Search Accommodation](/docs/api/accommodations/search) — the `cluster` and `precision` params # Overview (/docs/api/concepts) These pages explain how the Direct Travel API behaves so the endpoint reference can stay short. * [Locations & geocoding](/docs/api/concepts/locations) — the four ways to say where to search * [Response & supplier model](/docs/api/concepts/response-model) — what a result looks like and where the suppliers live * [Pricing & quotes](/docs/api/concepts/pricing) — when prices appear and what they mean * [Pagination](/docs/api/concepts/pagination) — paging through results and reading `meta.total` * [Clustering](/docs/api/concepts/clustering) — summarizing dense map viewports into H3 markers * [Caching](/docs/api/concepts/caching) — caching responses and reading `X-Cache` # Locations & geocoding (/docs/api/concepts/locations) Every search needs a location, supplied as one of four inputs. Provide at least one, or the request fails with `LOCATION_REQUIRED`. | Input | Parameters | When to use | Notes | | ------------ | ---------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------- | | Address | `address` | A city, region, or place name you have as text | Geocoded server-side; less precise than coordinates | | Coordinates | `lat` + `lng` (+ `radius`) | You already have a point and want the most precise, fastest search | Skips geocoding | | Bounding box | `nelat`, `nelng`, `swlat`, `swlng` | Map UIs searching the visible viewport | All four corners are required together | | Hotel IDs | `hotelids` | Looking up specific known stays | Can be Stay22 hotel IDs or Hotel IDs from the providers | ## Points of interest [#points-of-interest] For a landmark or venue (e.g. "Eiffel Tower", "Bell Centre"), geocode the point of interest to coordinates first, then search with `lat`/`lng` + `radius`. This is faster and more precise than passing the name as `address`. ## Priority [#priority] When both `address` and `lat`/`lng` are present, coordinates win; the address is used only as a fallback and for display context. ## What to read next [#what-to-read-next] * [Response & supplier model](/docs/api/concepts/response-model) — the shape of a result * [Pricing & quotes](/docs/api/concepts/pricing) — add dates to get live prices * [Search Accommodation](/docs/api/accommodations/search) — every location parameter, with a live playground # Pagination (/docs/api/concepts/pagination) The search pages results with `page` (1-indexed) and `pageSize` (≤100); follow `_links.next` until it is absent. `meta.hasMore` is the source of truth for "is there another page?" ## What `meta.total` means [#what-metatotal-means] `meta.total` is a bounded count of the stays you can page through for this request, and its meaning depends on the request: | Request | `meta.total` is | Caveat | | ----------------------------- | -------------------------------------------------- | -------------------------------------------------------- | | Static (no dates) | Stays matching your geo + predicate filters | Saturates at \~100,000 for very broad viewports | | Priced (`checkin`/`checkout`) | Stays that survived availability + price filtering | `total` and `hasMore` stay consistent with `_links.next` | | `cluster=top` | Candidate areas (H3 cells) in the viewport | A lower bound for very dense viewports | ## How to page [#how-to-page] Follow `_links.next` until it is absent or `meta.hasMore` is `false`. Don't compute page counts from `meta.total` alone — treat `hasMore`/`_links.next` as authoritative. ## What to read next [#what-to-read-next] * [Clustering](/docs/api/concepts/clustering) — how `cluster=top` changes `meta.total` * [Response & supplier model](/docs/api/concepts/response-model) — the shape you're paging through * [Search Accommodation](/docs/api/accommodations/search) — `page`/`pageSize` on the search endpoint # Pricing & quotes (/docs/api/concepts/pricing) Price quotes appear only when you search with dates. Omit `checkin`/`checkout` and results are static — no quotes, and the price filters are no-ops. ## Where prices live [#where-prices-live] Per-supplier quotes live under `suppliers..price`, with `total` for the full stay in the response currency. Divide by `meta.nights` for the per-night price. Set the response currency with `currency`. ## Price filters [#price-filters] The `min` and `max` filters apply to the **per-night price in USD** and only run when `checkin`/`checkout` are also supplied. ## Date rules [#date-rules] Explicit dates must be today or later and no more than two years in the future. Omitting dates yields static results. Searching without dates is the fastest path when you only need availability and listings, not live prices. ## What to read next [#what-to-read-next] * [Response & supplier model](/docs/api/concepts/response-model) — where prices live in a result * [Caching](/docs/api/concepts/caching) — cache priced responses safely * [Locations & geocoding](/docs/api/concepts/locations) — where to search # Response & supplier model (/docs/api/concepts/response-model) Every search returns `results[]`, `meta`, and `_links`. Each `results[]` entry is one unified stay per property, carrying a `suppliers.` map across Booking.com, VRBO, Expedia, and Hotels.com. ## The stay [#the-stay] Each stay holds the property-level fields: * `id`, `name`, `type` * `location` (`address`, `coordinates`, `distanceInMeters`), `rating`, `capacity`, `policies`, `media` * `url` — a Stay22 deeplink carrying your `aid`, so the booking is tracked to you `meta` carries `currency`, `page`, `pageSize`, `total`, and `hasMore` (plus `checkin`, `checkout`, and `nights` for dated searches); `_links.self` is the canonical URL for the request and `_links.next` pages forward. See [Pagination](/docs/api/concepts/pagination). ## Suppliers [#suppliers] The `suppliers.` map (e.g. `booking`, `vrbo`, `expedia`, `hotelscom`) holds, per supplier: * `id` and `link` — a Stay22 deeplink to that supplier, carrying your `aid` * `price` — present only when the search ran with dates; `price.total` is the full-stay amount in `meta.currency` (divide by `meta.nights` for per-night) The `provider` filter is optional: omit it to return all suppliers, or pass `provider=booking|vrbo|expedia|hotelscom` to narrow the results to one. In clustered responses, sparse stays flow through `results[]` and dense areas through `clusters[]`; a stay never appears in both. See [Clustering](/docs/api/concepts/clustering). ## What to read next [#what-to-read-next] * [Pagination](/docs/api/concepts/pagination) — page through results * [Clustering](/docs/api/concepts/clustering) — H3 markers for dense viewports * [Pricing & quotes](/docs/api/concepts/pricing) — when prices appear # Code Examples (/docs/api/recipes/code-examples) Examples for the unified search, which returns one stay per property with a `suppliers` map across Booking.com, VRBO, Expedia, and Hotels.com in a single request. ## Unified search [#unified-search] ```bash # searches all suppliers at once — no provider required curl -H "X-API-KEY: " \ "https://api.stay22.com/v2/accommodations?address=Paris" # get at hub.stay22.com — or omit the header for demo mode ``` Each result carries a `suppliers.` map; `suppliers..link` is a Stay22 deeplink that carries your `aid`: ```json { "meta": { "page": 1, "pageSize": 10, "total": 29758, "hasMore": true, "currency": "USD" }, "results": [ { "id": "42f1014f...0000", "url": "https://www.stay22.com/allez/roam/...?aid=", "name": "Appartement aux portes de Paris", "suppliers": { "booking": { "id": "15711178", "link": "https://www.stay22.com/allez/booking/15711178?aid=" } } } ], "_links": { "next": "https://api.stay22.com/v2/accommodations?address=Paris&page=2" } } ``` See [Response & supplier model](/docs/api/concepts/response-model). ## Paginate [#paginate] To page through results, follow `_links.next` until it is absent (see [Pagination](/docs/api/concepts/pagination)). ```javascript async function* allStays(params) { let url = `https://api.stay22.com/v2/accommodations?${new URLSearchParams(params)}`; while (url) { const res = await fetch(url, { headers: { 'X-API-KEY': process.env.API22_API_KEY } }); const { results, _links } = await res.json(); yield* results; url = _links.next ?? null; } } ``` ## Filter by supplier [#filter-by-supplier] ```bash curl -H "X-API-KEY: " \ "https://api.stay22.com/v2/accommodations?address=Paris&provider=booking" ``` ## Add dates for live prices [#add-dates-for-live-prices] With `checkin`/`checkout`, each supplier gains a `price` (see [Pricing & quotes](/docs/api/concepts/pricing)): ```python import requests, os res = requests.get( 'https://api.stay22.com/v2/accommodations', headers={'X-API-KEY': os.getenv('API22_API_KEY')}, params={'address': 'New York City', 'checkin': '2026-07-01', 'checkout': '2026-07-04'}, ) data = res.json() ``` ## Cluster a dense map viewport [#cluster-a-dense-map-viewport] ```bash curl -H "X-API-KEY: " \ "https://api.stay22.com/v2/accommodations?nelat=48.90&nelng=2.42&swlat=48.80&swlng=2.25&cluster=auto" ``` See [Clustering](/docs/api/concepts/clustering). # Retrieve partner transaction (/docs/api/reporting/transactions) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}