The OTAless MCP server

One public, read-only MCP server over OTAless, the direct-booking channel for the agent era: stays published by independent property managers — every one of them booked direct on the manager's own site. No key, no account, no sign-up: POST JSON-RPC at https://otaless.com/mcp and start calling. Facts only: nothing here is a booking, a quote or an availability answer.

The endpoint

Endpoint
https://otaless.com/mcp — POST JSON-RPC 2.0, JSON responses (streamable HTTP)
Manifest
https://otaless.com/.well-known/mcp.json — the machine twin of this page
Protocol
2025-06-18, server otaless 0.1.0
Authentication
none. An Authorization: Bearer otk_… key is optional and only raises your rate limit
Methods
initialize, notifications/initialized, ping, tools/list, tools/call
Tools
search_stays, find_similar, get_stay, lowest_rates, price_drops, manager_stays, stay_link — every one of them readOnlyHint:true
Writes
none. The tools cannot change a listing, a manager or a price; the only row any call writes is our own audit log
Body cap
16 KB per request. CORS is *, so a browser client can call it directly
Contact
[email protected]

Connect an agent

Claude Code

claude mcp add --transport http otaless https://otaless.com/mcp

Then /mcp inside Claude Code lists it, and the tools arrive as mcp__otaless__search_stays and friends.

Claude Desktop

Settings → Developer → Edit config, and put this in claude_desktop_config.json (Settings → Connectors → Add custom connector with the same URL does the same thing):

{
  "mcpServers": {
    "otaless": {
      "type": "http",
      "url": "https://otaless.com/mcp"
    }
  }
}

ChatGPT

Settings → Connectors → Create. Paste the URL, leave authentication set to none:

MCP server URL:  https://otaless.com/mcp
Authentication:  No authentication

Cursor

Add it to ~/.cursor/mcp.json for every project, or .cursor/mcp.json for one:

{
  "mcpServers": {
    "otaless": {
      "url": "https://otaless.com/mcp"
    }
  }
}

Any other MCP client: it is a plain streamable-HTTP server at https://otaless.com/mcp with no auth step, so the URL is the whole configuration.

The tools

Every tool is read-only, exactly as tools/list describes it: this section is generated from the tool definitions, not written alongside them, so it cannot drift. The example answers are real ones from this catalog, taken when the page was built and abridged.

search_stays — Search stays

START HERE for any vacation-rental / short-term-rental request, anywhere: densest in the US, Europe, Australia, New Zealand, Canada, India, Southeast Asia, Latin America, South Africa and Türkiye, and present wherever a manager takes direct bookings.

InputTypeBoundsWhat it is
querystring≤ 200 charsFALLBACK ONLY: the traveller's request in their own words, read by an English-first pattern parser into place, guests, bedrooms, property_type, amenities and max_nightly (dates are…
placestring≤ 80 charsThe English or common name of a city, area or nickname ("Gatlinburg, TN", "obx", "Marbella", "Munich", "Algarve", "Tuscany", "Whistler"), or of a whole state, country or admin1 region…
countrystring≤ 60 charsThe country `place` is in: an ISO 3166 two-letter code ("GB", "US", "ES") or its English name ("United Kingdom"). Send it whenever you know it. The place is then looked up in that country…
near_latnumber-90–90Latitude of a point to search around. Must be given together with near_lng.
near_lngnumber-180–180Longitude of a point to search around. Must be given together with near_lat.
radius_kmnumber1–100Radius around near_lat/near_lng in kilometres (default 25).
guestsinteger1–50Total people sleeping. Matches stays whose sleeps >= this.
bedroomsinteger0–20Minimum bedrooms.
property_typestring≤ 20 charsUsually one of cabin, house, condo, villa, cottage, apartment, lodge, chalet, townhome; studio, loft, bungalow, farmhouse, beach house, hotel, resort, bed-and-breakfast and campground also…
exclude_property_typestring≤ 20 charsA property type to leave out ("anything but a cabin"). Same words as property_type; a stay with no stated type is kept.
exclude_lodgingstring≤ 30 charsA kind of lodging to leave out ("not a hotel", "no B&Bs"): hotel, bnb, inn, guesthouse, aparthotel, hostel, or room (every room type at a property). Same words as lodging; a stay with no…
lodgingstring≤ 30 charsKind of stay: entire_place (whole homes, no rooms), room (room types at small independent hotels, B&Bs, inns, guesthouses), or a property kind: hotel, bnb, inn, guesthouse, aparthotel…
amenitiesarray of string≤ 10 items, items ≤ 40 charsAll of these must be present. Say it however the traveller did - synonyms are normalised ("jacuzzi"/"spa" -> hot tub, "dogs allowed"/"pet friendly"/a dog or cat in the party -> pets…
max_nightlynumber0–1000000000The budget PER NIGHT, in `currency`, compared with each stay's published from-price (the starting nightly rate, not a quoted total). Convert it yourself before sending: a per-person budget…
currencystring≤ 3 chars, ^[A-Za-z]{3}$ISO currency ("EUR", "GBP", "USD"): keep ONLY stays whose published price is in it (a stay with no stated currency counts as priced in its country's), with `max_nightly` in it. Many stays…
limitinteger1–20Rows to return, 1-20 (default 8). from_nightly always describes the whole match; has_more says whether there is more of it.
cursorstring≤ 64 charsThe `next_cursor` of the previous page, to get the next one. Opaque and bound to the filters it was returned with: send exactly the same filters, or it is refused. Leave out for the first…

Example call — the params of a tools/call:

{
  "name": "search_stays",
  "arguments": {
    "place": "Orlando & Kissimmee, FL",
    "limit": 2
  }
}

What came back from this catalog just now, abridged:

{
  "place": {
    "slug": "orlando-kissimmee-fl",
    "name": "Orlando & Kissimmee, FL",
    "matched_by": "slug"
  },
  "from_nightly": {
    "amount": 51,
    "currency": "USD"
  },
  "has_more": true,
  "stays": [
    {
      "stay_id": "01M3M1FM32XCNRXK4ASXV3BWQN",
      "name": "Caribe Cove Breeze - Sleeps 6",
      "property_type": "condo",
      "bedrooms": 2,
      "bathrooms": 2,
      "sleeps": 6,
      "city": "Orlando & Kissimmee",
      "region": "FL",
      "country": "US",
      "geo_precision": "exact",
      "amenities": [
        "air conditioning",
        "bed linens",
        "blender",
        "…"
      ],
      "from_nightly": {
        "amount": 60,
        "currency": "USD"
      },
      "verified_at": "2026-10-29T18:37:40.867Z",
      "price_verified_at": "2026-10-29T18:37:40.867Z",
      "facts_complete": 1,
      "live": false,
      "manager": {
        "name": "Prime Vacation Rentals Across the U.S. & Hawaii"
      },
      "url": "https://otaless.com/stay/caribe-cove-breeze-sleeps-6-dgxgxi/",
      "amenities_truncated": true,
      "location_note": "located by the page's own coordinates in Orlando & Kissimmee, FL; the address text said \"Ladera Ranch, CA\", which is …"
    },
    {
      "stay_id": "01M3RE036V5CEVMN3J11E8C2J7",
      "name": "Sunlit Escape Resort Stay",
      "property_type": "condo",
      "bedrooms": 2,
      "bathrooms": 2,
      "sleeps": 6,
      "city": "Orlando & Kissimmee",
      "region": "FL",
      "country": "US",
      "geo_precision": "exact",
      "amenities": [
        "air conditioning",
        "baking sheet",
        "bathtub",
        "…"
      ],
      "from_nightly": {
        "amount": 60,
        "currency": "USD"
      },
      "verified_at": "2026-10-28T16:25:24.270Z",
      "price_verified_at": "2026-10-28T16:25:24.270Z",
      "facts_complete": 1,
      "live": false,
      "manager": {
        "name": "Prime Vacation Rentals Across the U.S. & Hawaii"
      },
      "url": "https://otaless.com/stay/sunlit-escape-resort-stay-hj08sw/",
      "amenities_truncated": true,
      "location_note": "located by the page's own coordinates in Orlando & Kissimmee, FL; the address text said \"Ladera Ranch, CA\", which is …"
    }
  ],
  "next_cursor": "eyJvIjoyLCJrIjoiYWRyZlRtWVYifQ",
  "next_step": "Pick a stay_id, then call stay_link with it to get the direct booking link. stay_link is the only source of a booking …"
}

find_similar — Find a similar stay

Paste a listing from anywhere - Airbnb, Vrbo, Booking, a manager's own page - and get the same stay or the closest direct-booking matches in this catalog.

InputTypeBoundsWhat it is
listingstring≤ 8000 charsThe pasted listing page as text: title line, "Entire apartment in <City>, <Region>, <Country>", "4 guests 1 bedroom 2 beds 1 bathroom", "Hosted by <Name>", the amenity list and the price…
titlestring≤ 140 charsThe pasted property's own name or headline, as its page shows it. Compared with each stay's name on distinctive words only; never a filter.
hoststring≤ 80 charsWho the page says hosts or manages it ("Hosted by <Name>": a person or a company). Compared with each stay's manager name, ignoring case, punctuation and words like LLC or Vacation Rentals.
placestring≤ 80 charsWhere the pasted property is: the English or common name of its city, area or nickname ("Panama City Beach, FL", "obx", "Algarve", "Munich"), or a state, region or country. Overrides the…
countrystring≤ 60 charsThe country of the pasted property: ISO 3166 two-letter code ("GB") or English name. The place is looked up in that country only; it is not a separate filter.
near_latnumber-90–90Latitude of the pasted property, if you know it. Candidates within 20 km are considered. Must be given together with near_lng.
near_lngnumber-180–180Longitude of the pasted property. Must be given together with near_lat.
bedroomsinteger0–20Bedrooms of the pasted property (0 for a studio). Not a filter: it is scored, and an exact match on it is what makes `exact_match` possible.
bathroomsnumber0–20Bathrooms of the pasted property; halves allowed (1.5).
guestsinteger1–50How many the pasted property sleeps. A stay that sleeps the same or two more scores best; one that publishes fewer is not in `matches`.
property_typestring≤ 20 charsOne of cabin, house, condo, villa, cottage, apartment, lodge, chalet, townhome. Plurals and near-synonyms ("townhouse", "loft", "bungalow") are normalised; condo/apartment/townhome count…
property_namestring≤ 140 charsThe name of the property the page is about, when it is a hotel, B&B, inn, guesthouse, aparthotel or hostel ("Rose Cottage B&B", "Hotel Villa Rosa"), or a whole villa or apartment on a…
lodging_classstring≤ 30 charsWhat kind of lodging the page's property is: hotel, bnb, inn, guesthouse, aparthotel, hostel, or vacation_rental for a whole villa, apartment or home. Traveller words work ("bed and…
lodgingstring≤ 30 charsWhether the page's bookable unit is a room at the property ("room") or the whole place ("entire_place"). A class word is read as lodging_class.
amenitiesarray of string≤ 40 items, items ≤ 40 charsAmenity names from the pasted page, in any wording - the page's whole list is fine. Synonyms are normalised the same way search_stays normalises them, and at most ten are scored, the…
nightlynumber0–1000000000The other site's price PER NIGHT, so `nightly_ratio` can say how our published from-price compares. Never a stay total: for "$2,450 total · 7 nights" send total_price 2450 and nights 7…
total_pricenumber0–100000000000The other site's price for the whole stay, when the page shows a total rather than a nightly rate. Send it with `nights`; the nightly figure is total_price / nights. Not with `nightly`.
nightsinteger1–365The nights `total_price` covers ("7 nights"). At least 1.
currencystring≤ 3 chars, ^[A-Za-z]{3}$ISO currency of `nightly` or `total_price` ("USD", "EUR", "INR"). A different currency from ours is compared at the European Central Bank's reference rate, marked approximate and dated.
limitinteger1–20Rows in `matches`, 1-20 (default 8), best first.
source_urlstring≤ 400 charsThe page the pasted text came from (an http(s) URL). Recorded as host and path only, never fetched. It changes the match only when it is a stay's own page on the manager's site: that stay…

Example call — the params of a tools/call:

{
  "name": "find_similar",
  "arguments": {
    "listing": "Entire cabin in Orlando & Kissimmee, FL\n8 guests 3 bedrooms 2 bathrooms\nHot tub\nWifi",
    "limit": 2
  }
}

What came back from this catalog just now, abridged:

{
  "interpreted": {
    "title": null,
    "host": null,
    "place": "Orlando & Kissimmee, FL",
    "place_tried": [
      "Orlando & Kissimmee, FL",
      "Orlando & Kissimmee"
    ],
    "guests": 8,
    "bedrooms": 3,
    "bathrooms": 2,
    "beds": null,
    "property_type": "cabin",
    "amenities": [
      "hot tub",
      "wifi"
    ],
    "nightly": null,
    "currency": null,
    "point": {
      "lat": 28.4,
      "lng": -81.5,
      "source": "place_centroid"
    },
    "from_listing": [
      "place",
      "guests",
      "bedrooms",
      "…"
    ],
    "from_arguments": [],
    "note": "`from_listing` lists facts read out of the pasted text rather than passed by you - check them, a misread bedroom count …"
  },
  "place": {
    "slug": "orlando-kissimmee-fl",
    "name": "Orlando & Kissimmee, FL",
    "matched_by": "slug"
  },
  "exact_match": null,
  "exact_match_reason": null,
  "matches": [
    {
      "stay_id": "01M4606AXFKHVJ95TBAC7GSDHN",
      "name": "Doris's Place- Family Fun, Air Hockey, Near Trails",
      "property_type": "cabin",
      "stay_kind": "room",
      "lodging_class": "bnb",
      "property_name": "BnB Direct Booking",
      "bedrooms": 3,
      "bathrooms": 2,
      "sleeps": null,
      "city": null,
      "region": "CA",
      "country": "US",
      "geo_precision": "inferred",
      "amenities": [
        "swimming pool",
        "spa",
        "kids play area.",
        "…"
      ],
      "from_nightly": null,
      "verified_at": "2026-10-27T16:57:35.723Z",
      "price_verified_at": null,
      "facts_complete": 0.67,
      "live": false,
      "manager": {
        "name": "BnB Direct Booking"
      },
      "url": "https://otaless.com/stay/doris-x27-s-place-family-fun-air-hockey-near-trail-r9ttzx/",
      "similarity": {
        "score": 0.9,
        "fit_score": 0.9,
        "distance_km": 0,
        "bedrooms_delta": 0,
        "sleeps_ok": null,
        "type_match": "same",
        "shared_amenities": [],
        "nightly_ratio": null,
        "same_place": true,
        "bathrooms_delta": 0,
        "name_match": null,
        "manager_match": null,
        "trust_cost": 0
      },
      "confidence": "medium",
      "why": [
        "same place",
        "same bedrooms",
        "same property type"
      ]
    },
    {
      "stay_id": "01M597T7YCZNS6V78RWJ71PRX1",
      "name": "Aparthotel Pool Days Plus Disney Nights Equal Best Family Vacation Ever! - 3016 Kissimmee",
      "property_type": "hotel",
      "stay_kind": "entire_place",
      "lodging_class": "hotel",
      "bedrooms": 3,
      "bathrooms": 2,
      "sleeps": 8,
      "city": null,
      "region": "FL",
      "country": "US",
      "geo_precision": "inferred",
      "amenities": [],
      "from_nightly": {
        "amount": 103,
        "currency": "USD"
      },
      "verified_at": "2026-10-19T04:48:14.284Z",
      "price_verified_at": "2026-10-19T04:48:14.284Z",
      "facts_complete": 0.83,
      "live": false,
      "manager": {
        "name": "Kissimmee hotels & apartments, all accommodations in Kissimmee"
      },
      "url": "https://otaless.com/stay/aparthotel-pool-days-plus-disney-nights-equal-best-51pizp/",
      "similarity": {
        "score": 1,
        "fit_score": 1,
        "distance_km": 0,
        "bedrooms_delta": 0,
        "sleeps_ok": true,
        "type_match": "different",
        "shared_amenities": [],
        "nightly_ratio": null,
        "same_place": true,
        "bathrooms_delta": 0,
        "name_match": null,
        "manager_match": null,
        "trust_cost": 0
      },
      "confidence": "medium",
      "why": [
        "same place",
        "same bedrooms",
        "sleeps 8"
      ]
    }
  ],
  "next_step": "Pick a stay - `exact_match` when it is not null, otherwise the first row of `matches` - and call stay_link with its …"
}

get_stay — Stay details

Every stored fact for one stay, by a stay_id that search_stays returned: type, bedrooms, bathrooms, sleeps, city/region, lat/lng, the manager's amenity names (every one stored, in the manager's order; past 200 `amenities_truncated` with `amenities_total`), the published…

InputTypeBoundsWhat it is
stay_id requiredstring≤ 64 charsThe stay_id string from a search_stays row.

Example call — the params of a tools/call:

{
  "name": "get_stay",
  "arguments": {
    "stay_id": "01M3M1FM32XCNRXK4ASXV3BWQN"
  }
}

What came back from this catalog just now, abridged:

{
  "stay_id": "01M3M1FM32XCNRXK4ASXV3BWQN",
  "name": "Caribe Cove Breeze - Sleeps 6",
  "property_type": "condo",
  "bedrooms": 2,
  "bathrooms": 2,
  "sleeps": 6,
  "city": "Orlando & Kissimmee",
  "region": "FL",
  "country": "US",
  "lat": 28.33733483,
  "lng": -81.65190361,
  "geo_precision": "exact",
  "amenities": [
    "air conditioning",
    "bed linens",
    "blender",
    "…"
  ],
  "from_nightly": {
    "amount": 60,
    "currency": "USD"
  },
  "location_note": "located by the page's own coordinates in Orlando & Kissimmee, FL; the address text said \"Ladera Ranch, CA\", which is …",
  "place": "orlando-kissimmee-fl",
  "updated": "2026-10-29T18:37:40.867Z",
  "verified_at": "2026-10-29T18:37:40.867Z",
  "price_verified_at": "2026-10-29T18:37:40.867Z",
  "facts_complete": 1,
  "live": false,
  "manager": {
    "name": "Prime Vacation Rentals Across the U.S. & Hawaii"
  },
  "url": "https://otaless.com/stay/caribe-cove-breeze-sleeps-6-dgxgxi/",
  "first_seen": "2026-09-28T12:57:49.922Z",
  "verified_direct": {
    "checked_since": "2026-09-28T12:57:49.922Z",
    "last_checked": "2026-10-29T18:37:40.867Z",
    "price_checked": "2026-10-29T18:37:40.867Z",
    "licence_number": null,
    "listed_on": [],
    "manager_size": "many"
  },
  "next_step": "Call stay_link with this stay_id to get the direct booking link. These facts are live:false (crawled, not read live) …"
}

lowest_rates — Lowest published rates

The cheapest stays in a place by their PUBLISHED starting nightly rate, cheapest first.

InputTypeBoundsWhat it is
querystring≤ 200 charsFALLBACK ONLY: the traveller's request in their own words, read like search_stays' `query` by an English-first parser. Structured fields win over the parse; prefer them.
placestring≤ 80 charsThe English or common name of a city, area, region, state or country, as search_stays takes it. Required unless near_lat+near_lng (or `country` alone, or a query naming a place) are given.
countrystring≤ 60 charsThe country `place` is in: ISO 3166 two-letter code or English name. The place is looked up in that country only; alone, it is the whole country.
near_latnumber-90–90Latitude of a point to search around; with near_lng.
near_lngnumber-180–180Longitude of a point to search around; with near_lat.
radius_kmnumber1–100Radius around the point in kilometres (default 25).
guestsinteger1–50Stays that sleep at least this many.
bedroomsinteger0–20Minimum bedrooms.
property_typestring≤ 20 charsAs search_stays' property_type.
exclude_property_typestring≤ 20 charsA property type to leave out.
exclude_lodgingstring≤ 30 charsA kind of lodging to leave out, as search_stays' exclude_lodging.
lodgingstring≤ 30 charsAs search_stays' lodging.
amenitiesarray of string≤ 10 items, items ≤ 40 charsAll of these must be listed, as in search_stays.
max_nightlynumber0–1000000000The budget PER NIGHT, in `currency`, compared with each stay's published from-price (the starting nightly rate, not a quoted total). Convert it yourself before sending: a per-person budget…
currencystring≤ 3 chars, ^[A-Za-z]{3}$Only stays priced in this ISO currency ("EUR", "GBP", "USD").
per_currencyinteger1–20Stays per currency group, 1-20 (default 8).

Example call — the params of a tools/call:

{
  "name": "lowest_rates",
  "arguments": {
    "place": "Orlando & Kissimmee, FL",
    "per_currency": 2
  }
}

What came back from this catalog just now, abridged:

{
  "place": {
    "slug": "orlando-kissimmee-fl",
    "name": "Orlando & Kissimmee, FL",
    "matched_by": "slug"
  },
  "groups": [
    {
      "currency": "USD",
      "stays": [
        {
          "stay_id": "01M597T80VGGYT2V6MYQ1RP8XJ",
          "name": "Hotel Budgetel Inn Kissimmee",
          "property_type": "hotel",
          "stay_kind": "entire_place",
          "lodging_class": "hotel",
          "bedrooms": null,
          "bathrooms": null,
          "sleeps": null,
          "city": null,
          "region": "FL",
          "country": "US",
          "geo_precision": "inferred",
          "amenities": [],
          "from_nightly": {
            "amount": 51,
            "currency": "USD"
          },
          "verified_at": "2026-10-19T04:48:14.363Z",
          "price_verified_at": "2026-10-19T04:48:14.363Z",
          "facts_complete": 0.5,
          "live": false,
          "manager": {
            "name": "Kissimmee hotels & apartments, all accommodations in Kissimmee"
          },
          "url": "https://otaless.com/stay/hotel-budgetel-inn-kissimmee-juyqgs/"
        },
        {
          "stay_id": "01M597T7YFKF0XM7ZZEX00SAAD",
          "name": "Hotel Holiday Inn & Suites Orlando Sw - Celebration Area By Ihg Kissimmee",
          "property_type": "hotel",
          "stay_kind": "entire_place",
          "lodging_class": "hotel",
          "bedrooms": null,
          "bathrooms": null,
          "sleeps": null,
          "city": null,
          "region": "FL",
          "country": "US",
          "geo_precision": "inferred",
          "amenities": [],
          "from_nightly": {
            "amount": 51,
            "currency": "USD"
          },
          "verified_at": "2026-10-19T04:48:14.287Z",
          "price_verified_at": "2026-10-19T04:48:14.287Z",
          "facts_complete": 0.5,
          "live": false,
          "manager": {
            "name": "Kissimmee hotels & apartments, all accommodations in Kissimmee"
          },
          "url": "https://otaless.com/stay/hotel-holiday-inn-suites-orlando-sw-celebration-ar-a8popa/"
        }
      ]
    },
    {
      "currency": "EUR",
      "stays": [
        {
          "stay_id": "01M460WQ81NFMHC63K90YKATF0",
          "name": "Appartement op de bovenste verdieping, in een omheind resort in Kissimmee - Florida - Verenigde Staten",
          "property_type": null,
          "bedrooms": null,
          "bathrooms": null,
          "sleeps": null,
          "city": "Kissimmee",
          "region": "FL",
          "country": "US",
          "geo_precision": "exact",
          "amenities": [],
          "from_nightly": {
            "amount": 62,
            "currency": "EUR"
          },
          "verified_at": "2026-10-09T15:32:42.078Z",
          "price_verified_at": "2026-10-09T15:32:42.078Z",
          "facts_complete": 0.5,
          "live": false,
          "manager": {
            "name": "Vakantiehuis huren bij Micazu"
          },
          "url": "https://otaless.com/stay/appartement-op-de-bovenste-verdieping-in-een-omhei-6obhsu/"
        },
        {
          "stay_id": "01M634TW7QP1X32DGCK2AVPF9B",
          "name": "Eigentumswohnung auf der obersten Etage, in geschlossenem Resort in Kissimmee - Florida - USA",
          "property_type": "condo",
          "bedrooms": 3,
          "bathrooms": 2,
          "sleeps": 6,
          "city": "Kissimmee",
          "region": "FL",
          "country": "US",
          "geo_precision": "exact",
          "amenities": [],
          "from_nightly": {
            "amount": 69,
            "currency": "EUR"
          },
          "verified_at": "2026-10-29T06:16:24.567Z",
          "price_verified_at": "2026-10-29T06:16:24.567Z",
          "facts_complete": 0.83,
          "live": false,
          "manager": {
            "name": "Ferienhaus mieten bei Micazu"
          },
          "url": "https://otaless.com/stay/eigentumswohnung-auf-der-obersten-etage-in-geschlo-ksnifi/"
        }
      ]
    }
  ],
  "rates": "published starting nightly rates, crawled from each manager's site; not live, not for any date, fees and taxes not …",
  "next_step": "These are published starting rates, not quotes: say \"from\". Pick a stay_id and call stay_link for the booking link …"
}

price_drops — Recent price drops

Stays in one town or city whose PUBLISHED starting nightly rate fell lately, the steepest fall first.

InputTypeBoundsWhat it is
place requiredstring≤ 80 charsThe English or common name of a town or city, as search_stays takes it.
countrystring≤ 60 charsThe country `place` is in: ISO 3166 two-letter code or English name. The place is looked up in that country only.
limitinteger1–20Rows to return, 1-20 (default 8).

manager_stays — More from this manager

The other stays of the manager behind one stay, nearest first: what else the same company lets, booked on the same site.

InputTypeBoundsWhat it is
stay_id requiredstring≤ 64 charsThe stay_id of a stay from search_stays, lowest_rates or find_similar.
limitinteger1–20Rows to return, 1-20 (default 8).

Example call — the params of a tools/call:

{
  "name": "manager_stays",
  "arguments": {
    "stay_id": "01M3M1FM32XCNRXK4ASXV3BWQN",
    "limit": 2
  }
}

What came back from this catalog just now, abridged:

{
  "stay_id": "01M3M1FM32XCNRXK4ASXV3BWQN",
  "manager": {
    "name": "Prime Vacation Rentals Across the U.S. & Hawaii"
  },
  "stays": [
    {
      "stay_id": "01M3QQQVVRC4FPZ6DBWB22GGH4",
      "name": "Caribe Cove Charm - For 8!",
      "property_type": "condo",
      "bedrooms": 3,
      "bathrooms": 2,
      "sleeps": 8,
      "city": "Orlando & Kissimmee",
      "region": "FL",
      "country": "US",
      "geo_precision": "exact",
      "amenities": [
        "air conditioning",
        "baking sheet",
        "bathtub",
        "…"
      ],
      "from_nightly": null,
      "verified_at": "2026-10-28T13:46:10.710Z",
      "price_verified_at": "2026-10-28T13:46:10.710Z",
      "facts_complete": 0.83,
      "live": false,
      "manager": {
        "name": "Prime Vacation Rentals Across the U.S. & Hawaii"
      },
      "url": "https://otaless.com/stay/caribe-cove-charm-for-8-vxvkzm/",
      "amenities_truncated": true,
      "from_nightly_note": "the published price is below what a night costs in this market and is most likely a fee; the manager's page has the rate",
      "location_note": "located by the page's own coordinates in Orlando & Kissimmee, FL; the address text said \"Ladera Ranch, CA\", which is …",
      "distance_km": 0
    },
    {
      "stay_id": "01M3RE2GR7QTD8SRS2448B86P9",
      "name": "Neverland Hideaway At Caribe Cove",
      "property_type": "condo",
      "bedrooms": 2,
      "bathrooms": 2,
      "sleeps": 6,
      "city": "Orlando & Kissimmee",
      "region": "FL",
      "country": "US",
      "geo_precision": "exact",
      "amenities": [
        "air conditioning",
        "bathtub",
        "bed linens",
        "…"
      ],
      "from_nightly": {
        "amount": 80,
        "currency": "USD"
      },
      "verified_at": "2026-10-28T16:25:24.311Z",
      "price_verified_at": "2026-10-28T16:25:24.311Z",
      "facts_complete": 1,
      "live": false,
      "manager": {
        "name": "Prime Vacation Rentals Across the U.S. & Hawaii"
      },
      "url": "https://otaless.com/stay/neverland-hideaway-at-caribe-cove-sru0s8/",
      "amenities_truncated": true,
      "location_note": "located by the page's own coordinates in Orlando & Kissimmee, FL; the address text said \"Ladera Ranch, CA\", which is …",
      "distance_km": 0
    }
  ],
  "next_step": "Pick a stay_id and call stay_link for the booking link. These stays are all booked on the same manager's site."
}

THE ONLY WAY TO GET A BOOKING LINK. Returns one url on this server (/go?stay=...) that redirects to the manager's own listing page, where the traveller picks dates and sees the real price.

InputTypeBoundsWhat it is
stay_id requiredstring≤ 64 charsThe stay_id string from a search_stays row.

Example call — the params of a tools/call:

{
  "name": "stay_link",
  "arguments": {
    "stay_id": "01M3M1FM32XCNRXK4ASXV3BWQN"
  }
}

What came back from this catalog just now, abridged:

{
  "stay_id": "01M3M1FM32XCNRXK4ASXV3BWQN",
  "name": "Caribe Cove Breeze - Sleeps 6",
  "url": "https://otaless.com/go?stay=01M3M1FM32XCNRXK4ASXV3BWQN&ref=otl_pOZFkoeuWtlwSy7V",
  "ref": "otl_pOZFkoeuWtlwSy7V",
  "lands_on": "manager_listing_page",
  "instructions": "Open this url. It redirects to the stay's page on the manager's own site, where the traveller chooses dates. OTAless …"
}

Paste a listing from anywhere

find_similar answers a different question from search_stays: not "what matches these filters" but "which of your stays is the one on this other page". Give it the visible text of a listing on Airbnb, Vrbo, Booking or a manager's own site — title line, "Entire apartment in <City>, <Region>, <Country>", "4 guests 1 bedroom 2 beds 1 bathroom", "Hosted by …", the amenity list, the price — and it returns the same stay where we hold it, or the nearest direct-booking alternatives.

What it reads
the place (tried as the whole phrase, then city + region, then the city), guests, bedrooms (a studio is 0), bathrooms (1.5 counts), beds, property type, amenity names, the title, the host, and a nightly price — ₹26,962 for 2 nights becomes 13,481 a night, and Indian and European digit grouping both parse. The parse comes back as interpreted, with from_listing and from_arguments saying which fact came from where
Two tiers
an exact_match is the same stay or property (same place within 2 km, the same size, and the same manager, name or property name; a pasted pin needs a stay with a point). Otherwise matches are the closest stays within 10 km that sleep the party, each with a confidence (high or medium), the why words and the similarity breakdown; a stay with no point is never “nearby”, and when nothing fits the answer is an empty list with a reason
score
lower is closer, 0 means every pasted fact matched. It adds up bedrooms (exact 0, ±1 0.6, further 1.5), size against the headcount (right-sized 0, roomy 0.3, too small 1.0), bathrooms (0.3 per unit, capped), property type (same 0, condo/apartment/townhome as one group 0.2, different 0.6), distance (km ÷ 5, capped at 3), amenity overlap (up to 0.4) and the nightly ratio (|ln| of ours over theirs, capped at 1.0, same currency only), and, when the listing carried them, its title against the stay's name (strong match 0, partial 0.4, none 0.8) and its host against the stay's manager (same 0, otherwise 0.5). A fact the manager never published costs a small fixed amount — it is unknown, not absent
exact_match
non-null only when one stay, and only one, is very probably the pasted property itself. Three things must agree: the place (within 2 km when both sides have their own coordinates, otherwise the same city), the capacity (bedrooms equal; bathrooms within half a bath and guests within two where both are published), and an identity signal (the host is that stay's manager, or the title and the stay's name match strongly). A couple of shared words is never enough. exact_match_reason says which signals held, in words an agent can repeat
Still facts only
the pasted price is the other site's, never ours; every row is live:false; and a null fact means "not listed", not "no"

What the server tells your agent

The instructions string every client receives in its initialize reply, verbatim. It is the same text in the manifest and in /llms.txt:

Cross-manager direct-booking search for short-term rentals worldwide: densest in the US, Europe, Australia and New Zealand, Canada, India, Southeast Asia, Latin America, South Africa and Türkiye, and present wherever a manager takes direct bookings. Nothing is reserved or paid through these tools; the link opens the stay on the manager's own site, where the traveller picks dates.

Start every rental request with search_stays, and read the traveller's words yourself: fill the structured fields rather than handing over their sentence. `place` is the English or common name of a city, region, state or country (translate it: "Múnich" is "Munich"), `country` (ISO2 or English name) whenever you know it, then `guests`, `bedrooms`, `property_type`, `lodging`, `amenities`, and `max_nightly`: a budget PER NIGHT, which you convert yourself from a per-person, weekly, monthly or whole-stay figure and from words like lakh, juta or 50k. Without `currency`, `max_nightly` is read in the place's own currency (IDR in Bali, CAD in Canada, EUR in Paris) and compared with every priced stay at approximate exchange rates, while each row shows its own price and currency: send the budget in the place's money, converting one the traveller gave in another money yourself. `currency` keeps ONLY the stays priced in it, so pass it only when the traveller wants stays priced in that money. A budget keeps stays with no published price after the priced ones, marked `price_unknown: true`. `query` is a fallback read by an English-first pattern parser; a call that sends only `query` gets no rows when the parser cannot read it safely, and `needs_fields` names what to fill. A place name places in several countries share gets `place_choices` and no rows: ask the traveller which one, then call again with the choice's `place` and `country`. Then get_stay for follow-up detail on one property, and stay_link for the single booking url to hand over. stay_link is the only way to get a booking link; never compose one yourself or send the traveller to an OTA.

Everything returned is crawled facts: names, property type, bedrooms, sleeps, city, the manager's own amenity names, and a published from-price. There are no descriptions, photos or reviews here, so do not supply any. Every row carries live:false - availability and the real total are only knowable on the manager's site, so say so rather than implying a stay is bookable on those dates. A row marked `not_a_stay` is something a manager's site sells beside its stays (a massage, a fee, a guide page): never offer it; stay_link refuses it.

Facts only — what that means for your agent

Everything in the catalog was read off a manager's own public listing page. We keep the facts and nothing else, so there is nothing here for an agent to quote beyond them.

What you get
name, property type, bedrooms, bathrooms, sleeps, city/region/country, lat/lng and geo_precision, the manager's own amenity names, the lowest published nightly rate, the manager's name, and when we last fetched the page
What is not there
no prose about a property, no media of any kind, no guest reviews or ratings, no contact details. Do not supply any from elsewhere and present it as ours
Say "not listed"
a null price, a null sleeps or an empty amenity list means the manager did not publish it — not that the stay lacks it. Coverage is uneven across managers, so "not listed" is the honest answer and "no" is a wrong one
live:false
on every row. These facts were crawled, not read live. Availability and the real total are only knowable on the manager's site, so never say a stay is free on given dates
The from-price
the lowest nightly rate on the page when we last fetched it, before taxes, cleaning and fees. It is a starting rate, never a trip total and never a quote
geo_precision
exact (coordinates from the page), city (city centroid), inferred (city guessed from the URL, title or manager), or null

The /go handoff contract

stay_link is the only source of a booking URL. It returns one link on this server:

https://otaless.com/go?stay=<stay_id>&ref=otl_…
What it does
302s to the stay's own page on the manager's site, where the traveller picks dates and sees the real price. No dates or guests are passed. Never an open redirect: the target is always the listing URL we stored
Hand it over whole
give the traveller that URL verbatim. Do not shorten it, strip its parameters, substitute a manager domain, an OTA page or a search engine, or assemble a booking URL from anything else in a response — search rows deliberately carry none
No dates needed
stay_link takes only stay_id; tell the traveller to choose their dates on the manager's page
ref
an opaque otl_ token minted per link. It is how a click joins back to the call that produced it; it carries nothing about the traveller
Money
booking, payment and the final total all happen on the manager's site. OTAless takes no booking, no payment and no commission

WebMCP, on every page

An agent that simply lands on an OTAless page does not have to discover this endpoint first. Every page registers the same read-only tools in the browser through navigator.modelContext, from one cached script at /webmcp.js. On a place page it also gets search_here — search_stays with that page's place already filled in.

Registration is synchronous at parse time, so the tools are there the moment the document is. Each call is a same-origin POST to /mcp with credentials:'omit': no cookies, no storage, no third-party origin. Every page also carries <link rel="alternate" type="application/json" href="/.well-known/mcp.json"> and <meta name="mcp:endpoint" content="/mcp">.

Rate limits and API keys

No key is needed. The anonymous limits are generous enough for an assistant answering a traveller; a key exists for a partner doing more than that. Limits are per IP (or per key), never global.

WhatLimit
/mcp, anonymous60/min, 2,000/day
/mcp, with a keythe key's own quota
search_stays per session per hour600
/go30/min
Pages300/min
Anonymous response bytes per day50 MB

Over a limit is a 429 with Retry-After; the search_stays cap answers a JSON-RPC -32000 that says how to narrow the search instead. Five rate blocks in ten minutes buys the address an hour off. There is no bulk export: the catalog is for answering a traveller, one search at a time.

A key is Authorization: Bearer otk_… on /mcp, issued by hand: mail [email protected] with what you are building. An invalid or revoked key is a 401; it is never a requirement, only a bigger bucket.

Try it

Shake hands:

curl -sS -X POST https://otaless.com/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-06-18","capabilities":{},
                 "clientInfo":{"name":"curl","version":"1"}}}'

List the tools:

curl -sS -X POST https://otaless.com/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

Run a search — one sentence, no structured fields:

curl -sS -X POST https://otaless.com/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"search_stays",
                 "arguments":{"query":"cabin in Gatlinburg for 12 under $400 a night"}}}'

The other agent surfaces

/llms.txt
the whole site in one file: this endpoint, the tool names, the handoff contract, what we publish, and the place, state and country pages worth citing
/.well-known/mcp.json
the manifest: endpoint, protocol, methods, tool names and annotations, and the same instructions string
JSON twins
every catalog page answers in JSON too — https://otaless.com/search.json?q=…, /us/<st>.json, /<cc>/<region>/<place>.json, or Accept: application/json on the HTML URL
/sitemap.xml
the state, country, region, place and type and sleeps facet pages, one file per country; no stay or manager pages