Developers

Roogle MCP server

Roogle exposes its de-duplicated rental index to AI assistants over the Model Context Protocol. Read-only, three tools, one account per connection. This reference is English only.

For a walkthrough aimed at people rather than integrators, see /ai.

Endpoint and transport

Server URL

https://api.roogle.app/mcp
TransportStreamable HTTP
SessionStateless — no session id, no resumable stream
ResponsesJSON only; no SSE
Request headersContent-Type: application/json and an Accept carrying both application/json and text/event-stream
Serverroogle 1.0.0

Both the current protocol revision and the 2025-era revisions are accepted. The server has no sessions, so GET and DELETE on the endpoint answer 405.

Authorization

OAuth 2.1 against Roogle’s own authorization server — not a third-party identity provider. PKCE with S256 is mandatory, plain is refused, and the only grant types are authorization_code and refresh_token.

Issuerhttps://roogle.app
Authorizationhttps://roogle.app/oauth/authorize
Tokenhttps://roogle.app/api/oauth/token
Registration (RFC 7591)https://roogle.app/api/oauth/register
Protected resource metadatahttps://api.roogle.app/.well-known/oauth-protected-resource/mcp
Authorization server metadatahttps://roogle.app/.well-known/oauth-authorization-server
Scoperentals:read

What the server requires and guarantees

  • RFC 8707 resource indicators. The resource parameter is required at both the authorization and token endpoints, and becomes the token’s aud. A token minted for another resource is refused here.
  • RFC 9207. Every authorization response carries iss — including error redirects.
  • RFC 9728 / RFC 8414. Both discovery documents are served, and 401 replies name the protected-resource document in WWW-Authenticate.
  • RFC 8252 loopback. Redirect URIs are matched exactly, except that a loopback URI matches with the port ignored. localhost and 127.0.0.1 are not interchangeable.
  • Client ID Metadata Documents. An https client_id with a path is fetched and used as the registration — the document’s client_id must equal its own URL, and the redirect URI presented at authorization must appear in it. The fetch refuses redirects, private and loopback addresses, non-JSON, and anything over 64 KB, with a 5 s timeout; documents are cached, honouring no-store.
  • Dynamic client registration is open and always produces a public client — a request for client_secret_basic is downgraded, never refused. Unused dynamic clients are pruned, and the population is capped at 500.
  • Refresh rotation. A refresh token is single-use; reuse revokes the whole family.
CredentialLifetime
Authorization code60 s, single use
Access token600 s
Refresh token60 days idle, rotated on every use

A Roogle account is required: the connection is authorized by a human signing in, and every limit that applies on the website applies here. Access can be withdrawn at any time from Connected AI apps, which takes effect on the next request.

Connecting a client

Roogle is not listed in either assistant’s directory, so both paths below are the manual one. Menu labels last checked against the vendors’ own documentation on 2026-11-01.

ChatGPT
  1. Go to chatgpt.com/plugins
  2. Select the plus button, then Add custom MCP server
  3. Paste the server URL and configure authentication
  4. Confirm I understand and want to continue
  5. Choose Create as a plugin
Claude
  1. Customize → Connectors → + Add → Add custom connector
  2. Paste the server URL
  3. Authentication: Sign in now
  4. OAuth client: Register automatically

Use Claude's published identity also works — this server supports Client ID Metadata Documents.

Tools

Three tools, all read-only. The schemas below are generated from the same definitions the server registers, so they are what tools/list returns.

search_rentalsread-onlyidempotentopen world

Search Roogle's de-duplicated rental index by city, price, size, rooms, move-in date and offer type. Room counts are TOTAL rooms in the Dutch and Swiss sense (living room plus bedrooms), not bedrooms — a one-bedroom flat is 2 rooms. Coverage is the Netherlands and Switzerland. Returns one card per property, not one per post, with a roogle.app link for each. It does not return the original listing URL, the advertiser's contact details, or photos.

Input

FieldTypeRequiredDescription
citystringnoCity name, in any spelling. Matched against every spelling Roogle has stored for that place.
countrystringnoCountry name or ISO alpha-2 code. Omitted means every market Roogle covers; a country outside them returns no listings and says so.
minPriceintegernoLowest rent to include, in the listing's own currency.
maxPriceintegernoHighest rent to include, in the listing's own currency.
minRoomsintegernoFewest TOTAL rooms to include, counted the Dutch and Swiss way: the living room plus the bedrooms, excluding kitchen and bathroom. NOT a bedroom count — for two bedrooms ask for minRooms 3, for one bedroom ask for 2.
minSurfaceSqmintegernoSmallest floor area to include, in square metres.
maxSurfaceSqmintegernoLargest floor area to include, in square metres.
furnishedbooleannoTrue returns only listings the source said are furnished; false only those it said are not.
offerTypestring[]sublet | lease_takeover | room_flatshare | student_housing | standard_rental | agency_offer | house_villa | temporary_accommodationnoReturn only these kinds of offer. The codes describe the ARRANGEMENT, not the size or shape of the home: standard_rental (an ordinary tenancy), room_flatshare (a room in a shared home), sublet, lease_takeover, student_housing, agency_offer (posted by a letting agency), house_villa, temporary_accommodation. There is no code for a studio or a one-bedroom flat — size is expressed by minRooms and the surface filters. Remember minRooms counts TOTAL rooms, not bedrooms: a one-bedroom flat is minRooms 2.
availableFromstringnoA move-in date, YYYY-MM-DD. Returns listings already available on that day. A listing that states no dates is included.
withPhotosbooleannoTrue returns only listings whose photo actually displays on Roogle.
hidePaidSourcebooleannoTrue excludes listings whose source charges renters a fee to reach the advertiser.
sortstringnewest | price-asc | price-descnoResult order. Default newest.
limitintegernoHow many listings to return, up to 24. Default 8.
cursorstringnoA nextCursor from a previous call, to continue the same search.

Output

FieldTypeRequiredDescription
listingsobject[]yesThe matching listings, one entry per property rather than one per post.
listings[].idstringyesRoogle's own id for this listing. Stable, and the value get_rental and compare_rentals take.
listings[].roogleUrlstringyesThe listing's page on Roogle. The only link Roogle returns over this channel: the original post's URL, the advertiser's contact details and the photos are not part of any response.
listings[].citystring | nullyesThe city as the source wrote it. Spelling varies between sources for the same place.
listings[].districtstring | nullyesA neighbourhood or district name as the source wrote it, when there was one. Free text, not a boundary — it does not locate the property more precisely than the city.
listings[].countrystring | nullyesThe country as the source wrote it. Roogle covers the Netherlands and Switzerland.
listings[].priceobjectyesThe rent, as the source stated it.
listings[].price.amountnumber | nullyesThe rent figure the source stated, in the currency below. Roogle cannot tell whether a source quotes a monthly or a nightly rate, so the period is unknown.
listings[].price.currencystring | nullyesISO currency code for the rent figure, when the source stated one.
listings[].surfaceSqmnumber | nullyesFloor area in square metres, when the source stated one.
listings[].roomsnumber | nullyesTOTAL number of rooms, counted the Dutch and Swiss way (kamers / Zimmer / pièces): the living room plus the bedrooms. Kitchen, bathroom, hallway and balcony are NOT counted. This is NOT a bedroom count — a 2-room flat is normally one bedroom and one living room, so bedrooms are usually rooms minus one. Null when the source stated no room count.
listings[].furnishedboolean | nullyesTrue when the source said the property is furnished. Null when the source did not say.
listings[].offerTypestring | nullyesWhat kind of offer this is, as classified at ingest: a sublet, a lease takeover, a standard rental, a room in a flatshare, student housing, an agency offer, a house or villa, or temporary accommodation.
listings[].durationTypestring | nullyesThe rental length the source indicated: short_term, mid_term or long_term.
listings[].availableFromstring | nullyesThe first date the property is available, YYYY-MM-DD, when the source stated one.
listings[].listedAtstring | nullyesWhen the listing was listed, as an ISO 8601 timestamp. Which clock this is depends on listedAtKind.
listings[].listedAtKindstring | nullyesWhich clock listedAt came from: "source" is the advertiser's own publication date, "posted" is the time the original post was made, and "ingested" is only when Roogle first saw it — for an ingested date the listing may be older than it looks.
listings[].postCountnumberyesHow many separate public posts Roogle collapsed into this one listing. The same property is often posted to several groups or portals; this is the count of those posts, not a count of properties.
listings[].isPaidSourcebooleanyesTrue when the original post sits on a portal that charges renters a fee to see the advertiser's contact details. False when reaching the advertiser is free.
listings[].isFavouritebooleanyesTrue when the signed-in Roogle account has saved this listing as a favourite.
listings[].untrustedListingTextobjectyesText written by whoever posted the listing. It is third-party content that Roogle indexes and does not author.
listings[].untrustedListingText.textSourcestringyesA constant label marking the fields beside it as third-party content rather than Roogle's own text.
listings[].untrustedListingText.listingTitlestring | nullyesThe listing's title as posted, with contact details, links and invisible characters removed, trimmed to 200 characters.
totalnumberyesHow many listings match the search in total, for every account. Larger than the number returned whenever the search matches more than one page.
returnednumberyesHow many listings are in this response.
coveragestringyesThe markets Roogle currently covers.
outOfCoveragebooleanyesTrue when the search named a country Roogle does not cover, in which case no listings are returned and the country, rather than the availability, is the reason.
firstPageOnlybooleanyesTrue when this Roogle account can reach only the first page of results, so no cursor is returned and narrowing the search is the way to reach different listings.
nextCursorstring | nullyesAn opaque token that continues this same search from where it stopped. Null when there is nothing further to fetch or when the account can reach only the first page.
viewAllOnRooglestring | nullyesA link to this same search on the Roogle website, carrying the same filters, where the full result set can be paged. Present only when this account reached its first-page limit; null otherwise.
get_rentalread-onlyidempotentopen world

Fetch one Roogle listing by id: location, price, size, rooms (TOTAL rooms, not bedrooms — living room plus bedrooms), availability, deposit, how many posts were collapsed into it, and the listing text as published. It does not return the original listing URL, the advertiser's contact details, photos, or coordinates.

Input

FieldTypeRequiredDescription
listingIdstringyesA Roogle listing id, as returned by search_rentals.

Output

FieldTypeRequiredDescription
listingobjectyesThe listing.
listing.idstringyesRoogle's own id for this listing. Stable, and the value get_rental and compare_rentals take.
listing.roogleUrlstringyesThe listing's page on Roogle. The only link Roogle returns over this channel: the original post's URL, the advertiser's contact details and the photos are not part of any response.
listing.citystring | nullyesThe city as the source wrote it. Spelling varies between sources for the same place.
listing.districtstring | nullyesA neighbourhood or district name as the source wrote it, when there was one. Free text, not a boundary — it does not locate the property more precisely than the city.
listing.countrystring | nullyesThe country as the source wrote it. Roogle covers the Netherlands and Switzerland.
listing.priceobjectyesThe rent, as the source stated it.
listing.price.amountnumber | nullyesThe rent figure the source stated, in the currency below. Roogle cannot tell whether a source quotes a monthly or a nightly rate, so the period is unknown.
listing.price.currencystring | nullyesISO currency code for the rent figure, when the source stated one.
listing.surfaceSqmnumber | nullyesFloor area in square metres, when the source stated one.
listing.roomsnumber | nullyesTOTAL number of rooms, counted the Dutch and Swiss way (kamers / Zimmer / pièces): the living room plus the bedrooms. Kitchen, bathroom, hallway and balcony are NOT counted. This is NOT a bedroom count — a 2-room flat is normally one bedroom and one living room, so bedrooms are usually rooms minus one. Null when the source stated no room count.
listing.furnishedboolean | nullyesTrue when the source said the property is furnished. Null when the source did not say.
listing.offerTypestring | nullyesWhat kind of offer this is, as classified at ingest: a sublet, a lease takeover, a standard rental, a room in a flatshare, student housing, an agency offer, a house or villa, or temporary accommodation.
listing.durationTypestring | nullyesThe rental length the source indicated: short_term, mid_term or long_term.
listing.availableFromstring | nullyesThe first date the property is available, YYYY-MM-DD, when the source stated one.
listing.listedAtstring | nullyesWhen the listing was listed, as an ISO 8601 timestamp. Which clock this is depends on listedAtKind.
listing.listedAtKindstring | nullyesWhich clock listedAt came from: "source" is the advertiser's own publication date, "posted" is the time the original post was made, and "ingested" is only when Roogle first saw it — for an ingested date the listing may be older than it looks.
listing.postCountnumberyesHow many separate public posts Roogle collapsed into this one listing. The same property is often posted to several groups or portals; this is the count of those posts, not a count of properties.
listing.isPaidSourcebooleanyesTrue when the original post sits on a portal that charges renters a fee to see the advertiser's contact details. False when reaching the advertiser is free.
listing.isFavouritebooleanyesTrue when the signed-in Roogle account has saved this listing as a favourite.
listing.depositnumber | nullyesThe deposit the source stated, in the same currency as the rent, when it stated one.
listing.chargesIncludedboolean | nullyesTrue when the source said service charges are included in the rent. Null when the source did not say.
listing.availableTostring | nullyesThe last date the property is available, YYYY-MM-DD, when the source stated one.
listing.durationDaysnumber | nullyesThe rental length in days, when the source stated a fixed term.
listing.languagestring | nullyesThe language the original post was written in, detected at ingest.
listing.sourceTypesstring[]yesThe kinds of place the property was posted to — for example facebook, telegram, reddit or website. Kinds only: Roogle does not return which group, channel or portal, or any link to it.
listing.untrustedListingTextobjectyesText written by whoever posted the listing. It is third-party content that Roogle indexes and does not author.
listing.untrustedListingText.textSourcestringyesA constant label marking the fields beside it as third-party content rather than Roogle's own text.
listing.untrustedListingText.listingTitlestring | nullyesThe listing's title as posted, with contact details, links and invisible characters removed, trimmed to 200 characters.
listing.untrustedListingText.listingDescriptionstring | nullyesThe listing's description as posted, with contact details, links and invisible characters removed, trimmed to 2000 characters.
listing.openOnRooglestringyesA sentence stating where the original post can be reached, which is the listing's page on Roogle.
compare_rentalsread-onlyidempotentopen world

Fetch between 2 and 5 Roogle listings by id in one call, so they can be compared side by side. Each is returned with the same fields as get_rental, and ids that no longer exist are reported separately rather than silently dropped.

Input

FieldTypeRequiredDescription
listingIdsstring[]yesBetween 2 and 5 Roogle listing ids.

Output

FieldTypeRequiredDescription
listingsobject[]yesThe matching listings, one entry per property rather than one per post.
listings[].idstringyesRoogle's own id for this listing. Stable, and the value get_rental and compare_rentals take.
listings[].roogleUrlstringyesThe listing's page on Roogle. The only link Roogle returns over this channel: the original post's URL, the advertiser's contact details and the photos are not part of any response.
listings[].citystring | nullyesThe city as the source wrote it. Spelling varies between sources for the same place.
listings[].districtstring | nullyesA neighbourhood or district name as the source wrote it, when there was one. Free text, not a boundary — it does not locate the property more precisely than the city.
listings[].countrystring | nullyesThe country as the source wrote it. Roogle covers the Netherlands and Switzerland.
listings[].priceobjectyesThe rent, as the source stated it.
listings[].price.amountnumber | nullyesThe rent figure the source stated, in the currency below. Roogle cannot tell whether a source quotes a monthly or a nightly rate, so the period is unknown.
listings[].price.currencystring | nullyesISO currency code for the rent figure, when the source stated one.
listings[].surfaceSqmnumber | nullyesFloor area in square metres, when the source stated one.
listings[].roomsnumber | nullyesTOTAL number of rooms, counted the Dutch and Swiss way (kamers / Zimmer / pièces): the living room plus the bedrooms. Kitchen, bathroom, hallway and balcony are NOT counted. This is NOT a bedroom count — a 2-room flat is normally one bedroom and one living room, so bedrooms are usually rooms minus one. Null when the source stated no room count.
listings[].furnishedboolean | nullyesTrue when the source said the property is furnished. Null when the source did not say.
listings[].offerTypestring | nullyesWhat kind of offer this is, as classified at ingest: a sublet, a lease takeover, a standard rental, a room in a flatshare, student housing, an agency offer, a house or villa, or temporary accommodation.
listings[].durationTypestring | nullyesThe rental length the source indicated: short_term, mid_term or long_term.
listings[].availableFromstring | nullyesThe first date the property is available, YYYY-MM-DD, when the source stated one.
listings[].listedAtstring | nullyesWhen the listing was listed, as an ISO 8601 timestamp. Which clock this is depends on listedAtKind.
listings[].listedAtKindstring | nullyesWhich clock listedAt came from: "source" is the advertiser's own publication date, "posted" is the time the original post was made, and "ingested" is only when Roogle first saw it — for an ingested date the listing may be older than it looks.
listings[].postCountnumberyesHow many separate public posts Roogle collapsed into this one listing. The same property is often posted to several groups or portals; this is the count of those posts, not a count of properties.
listings[].isPaidSourcebooleanyesTrue when the original post sits on a portal that charges renters a fee to see the advertiser's contact details. False when reaching the advertiser is free.
listings[].isFavouritebooleanyesTrue when the signed-in Roogle account has saved this listing as a favourite.
listings[].depositnumber | nullyesThe deposit the source stated, in the same currency as the rent, when it stated one.
listings[].chargesIncludedboolean | nullyesTrue when the source said service charges are included in the rent. Null when the source did not say.
listings[].availableTostring | nullyesThe last date the property is available, YYYY-MM-DD, when the source stated one.
listings[].durationDaysnumber | nullyesThe rental length in days, when the source stated a fixed term.
listings[].languagestring | nullyesThe language the original post was written in, detected at ingest.
listings[].sourceTypesstring[]yesThe kinds of place the property was posted to — for example facebook, telegram, reddit or website. Kinds only: Roogle does not return which group, channel or portal, or any link to it.
listings[].untrustedListingTextobjectyesText written by whoever posted the listing. It is third-party content that Roogle indexes and does not author.
listings[].untrustedListingText.textSourcestringyesA constant label marking the fields beside it as third-party content rather than Roogle's own text.
listings[].untrustedListingText.listingTitlestring | nullyesThe listing's title as posted, with contact details, links and invisible characters removed, trimmed to 200 characters.
listings[].untrustedListingText.listingDescriptionstring | nullyesThe listing's description as posted, with contact details, links and invisible characters removed, trimmed to 2000 characters.
listings[].openOnRooglestringyesA sentence stating where the original post can be reached, which is the listing's page on Roogle.
unavailableobject[]yesThe requested ids that could not be returned, each with the reason: not_found when no visible listing has that id, out_of_market when the listing is outside the covered markets.
unavailable[].listingIdstringyesThe requested id.
unavailable[].reasonstringyesWhy it could not be returned: "not_found" or "out_of_market".
coveragestringyesThe markets Roogle currently covers.

search_rentals returns at most 24 listings per call (8 by default) and sorts by newest, price-asc, price-desc. compare_rentals takes 2 to 5 ids.

Rate limits and daily ceilings

ScopeLimit
Per user, tool calls60 / 60s
Per user, searches30 / 60s
Per user, OAuth endpoints20 / 60s
Client registration5 / 300s
Per client, all users600 / 60s

Daily tool calls, per UTC day

AccountCalls per day
Free100
Premium1000

A burst limit answers 429 with Retry-After. The daily ceiling is not an HTTP error — it arrives as a tool result with isError: true, because the call itself was valid.

Data limitations

  • Coverage is NL and CH. A search anywhere else returns a plain statement of coverage rather than an empty list.
  • One card per property, not one per post. Roogle collapses duplicate posts; sourceCount says how many were collapsed.
  • No original listing URL, and no advertiser contact details. Every listing carries a roogle.app link instead; the original is reachable from that page under the same rules as the website.
  • No images and no coordinates. The photos belong to whoever posted them.
  • No internal identifiers beyond the public listing id.
  • Listing text is third-party content. Titles are capped at 200 characters and descriptions at 2000, URLs and contact strings are stripped, and the text is delivered under a field named untrustedListingText precisely because it was written by someone else and should not be treated as instructions.

Errors

Transport and authorization failures are HTTP responses with an OAuth-shaped body (error, error_description).

StatuserrorWhen
401invalid_tokenNo bearer token, or one that is expired, unparseable, signed by another key, or issued for another resource. The WWW-Authenticate header names the protected resource metadata document; start the authorization flow.
401invalid_tokenThe connection was disconnected by the user, or the account was signed out everywhere. A refresh will not help — the human has to authorize again.
403access_deniedThe account is deleted, suspended, or scheduled for deletion. There is no tool that can undo any of those; the way back is the website.
403insufficient_scopeThe grant does not carry `rentals:read`.
429slow_downA per-user, per-client or per-IP burst limit. Retry-After carries the number of seconds.
415—The POST did not declare `Content-Type: application/json`.
405—A session operation from the 2025 protocol era (GET or DELETE on the endpoint). This server is stateless and has no sessions.
503temporarily_unavailableThe channel is not configured in this environment.
500server_errorThe server cannot verify tokens — a misconfiguration on our side. Retrying and re-authorizing will both fail; report it.

Tool failures are not HTTP errors

A tool that cannot answer returns a successful JSON-RPC result carrying { "isError": true, "content": [{ "type": "text", "text": "…" }] }. A protocol error means the call was malformed; this means the call was fine and the answer is no. It covers:

  • The daily tool-call ceiling for the account has been reached.
  • A listing id that does not exist, is no longer published, or sits outside the covered markets.
  • A cursor that is malformed or was issued for different filters.

Security contact

Report a vulnerability in this server to contact@roogle.app. Please include the request, the response and the time in UTC. Do not include another person’s account data in the report.

MCP server — developer reference | Roogle