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| Transport | Streamable HTTP |
| Session | Stateless — no session id, no resumable stream |
| Responses | JSON only; no SSE |
| Request headers | Content-Type: application/json and an Accept carrying both application/json and text/event-stream |
| Server | roogle 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.
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.
- Go to
chatgpt.com/plugins - Select the plus button, then Add custom MCP server
- Paste the server URL and configure authentication
- Confirm I understand and want to continue
- Choose Create as a plugin
- Customize → Connectors → + Add → Add custom connector
- Paste the server URL
- Authentication: Sign in now
- 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 worldSearch 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
| Field | Type | Required | Description |
|---|---|---|---|
| city | string | no | City name, in any spelling. Matched against every spelling Roogle has stored for that place. |
| country | string | no | Country name or ISO alpha-2 code. Omitted means every market Roogle covers; a country outside them returns no listings and says so. |
| minPrice | integer | no | Lowest rent to include, in the listing's own currency. |
| maxPrice | integer | no | Highest rent to include, in the listing's own currency. |
| minRooms | integer | no | Fewest 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. |
| minSurfaceSqm | integer | no | Smallest floor area to include, in square metres. |
| maxSurfaceSqm | integer | no | Largest floor area to include, in square metres. |
| furnished | boolean | no | True returns only listings the source said are furnished; false only those it said are not. |
| offerType | string[]sublet | lease_takeover | room_flatshare | student_housing | standard_rental | agency_offer | house_villa | temporary_accommodation | no | Return 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. |
| availableFrom | string | no | A move-in date, YYYY-MM-DD. Returns listings already available on that day. A listing that states no dates is included. |
| withPhotos | boolean | no | True returns only listings whose photo actually displays on Roogle. |
| hidePaidSource | boolean | no | True excludes listings whose source charges renters a fee to reach the advertiser. |
| sort | stringnewest | price-asc | price-desc | no | Result order. Default newest. |
| limit | integer | no | How many listings to return, up to 24. Default 8. |
| cursor | string | no | A nextCursor from a previous call, to continue the same search. |
Output
| Field | Type | Required | Description |
|---|---|---|---|
| listings | object[] | yes | The matching listings, one entry per property rather than one per post. |
| listings[].id | string | yes | Roogle's own id for this listing. Stable, and the value get_rental and compare_rentals take. |
| listings[].roogleUrl | string | yes | The 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[].city | string | null | yes | The city as the source wrote it. Spelling varies between sources for the same place. |
| listings[].district | string | null | yes | A 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[].country | string | null | yes | The country as the source wrote it. Roogle covers the Netherlands and Switzerland. |
| listings[].price | object | yes | The rent, as the source stated it. |
| listings[].price.amount | number | null | yes | The 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.currency | string | null | yes | ISO currency code for the rent figure, when the source stated one. |
| listings[].surfaceSqm | number | null | yes | Floor area in square metres, when the source stated one. |
| listings[].rooms | number | null | yes | TOTAL 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[].furnished | boolean | null | yes | True when the source said the property is furnished. Null when the source did not say. |
| listings[].offerType | string | null | yes | What 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[].durationType | string | null | yes | The rental length the source indicated: short_term, mid_term or long_term. |
| listings[].availableFrom | string | null | yes | The first date the property is available, YYYY-MM-DD, when the source stated one. |
| listings[].listedAt | string | null | yes | When the listing was listed, as an ISO 8601 timestamp. Which clock this is depends on listedAtKind. |
| listings[].listedAtKind | string | null | yes | Which 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[].postCount | number | yes | How 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[].isPaidSource | boolean | yes | True 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[].isFavourite | boolean | yes | True when the signed-in Roogle account has saved this listing as a favourite. |
| listings[].untrustedListingText | object | yes | Text written by whoever posted the listing. It is third-party content that Roogle indexes and does not author. |
| listings[].untrustedListingText.textSource | string | yes | A constant label marking the fields beside it as third-party content rather than Roogle's own text. |
| listings[].untrustedListingText.listingTitle | string | null | yes | The listing's title as posted, with contact details, links and invisible characters removed, trimmed to 200 characters. |
| total | number | yes | How many listings match the search in total, for every account. Larger than the number returned whenever the search matches more than one page. |
| returned | number | yes | How many listings are in this response. |
| coverage | string | yes | The markets Roogle currently covers. |
| outOfCoverage | boolean | yes | True 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. |
| firstPageOnly | boolean | yes | True 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. |
| nextCursor | string | null | yes | An 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. |
| viewAllOnRoogle | string | null | yes | A 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 worldFetch 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
| Field | Type | Required | Description |
|---|---|---|---|
| listingId | string | yes | A Roogle listing id, as returned by search_rentals. |
Output
| Field | Type | Required | Description |
|---|---|---|---|
| listing | object | yes | The listing. |
| listing.id | string | yes | Roogle's own id for this listing. Stable, and the value get_rental and compare_rentals take. |
| listing.roogleUrl | string | yes | The 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.city | string | null | yes | The city as the source wrote it. Spelling varies between sources for the same place. |
| listing.district | string | null | yes | A 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.country | string | null | yes | The country as the source wrote it. Roogle covers the Netherlands and Switzerland. |
| listing.price | object | yes | The rent, as the source stated it. |
| listing.price.amount | number | null | yes | The 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.currency | string | null | yes | ISO currency code for the rent figure, when the source stated one. |
| listing.surfaceSqm | number | null | yes | Floor area in square metres, when the source stated one. |
| listing.rooms | number | null | yes | TOTAL 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.furnished | boolean | null | yes | True when the source said the property is furnished. Null when the source did not say. |
| listing.offerType | string | null | yes | What 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.durationType | string | null | yes | The rental length the source indicated: short_term, mid_term or long_term. |
| listing.availableFrom | string | null | yes | The first date the property is available, YYYY-MM-DD, when the source stated one. |
| listing.listedAt | string | null | yes | When the listing was listed, as an ISO 8601 timestamp. Which clock this is depends on listedAtKind. |
| listing.listedAtKind | string | null | yes | Which 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.postCount | number | yes | How 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.isPaidSource | boolean | yes | True 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.isFavourite | boolean | yes | True when the signed-in Roogle account has saved this listing as a favourite. |
| listing.deposit | number | null | yes | The deposit the source stated, in the same currency as the rent, when it stated one. |
| listing.chargesIncluded | boolean | null | yes | True when the source said service charges are included in the rent. Null when the source did not say. |
| listing.availableTo | string | null | yes | The last date the property is available, YYYY-MM-DD, when the source stated one. |
| listing.durationDays | number | null | yes | The rental length in days, when the source stated a fixed term. |
| listing.language | string | null | yes | The language the original post was written in, detected at ingest. |
| listing.sourceTypes | string[] | yes | The 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.untrustedListingText | object | yes | Text written by whoever posted the listing. It is third-party content that Roogle indexes and does not author. |
| listing.untrustedListingText.textSource | string | yes | A constant label marking the fields beside it as third-party content rather than Roogle's own text. |
| listing.untrustedListingText.listingTitle | string | null | yes | The listing's title as posted, with contact details, links and invisible characters removed, trimmed to 200 characters. |
| listing.untrustedListingText.listingDescription | string | null | yes | The listing's description as posted, with contact details, links and invisible characters removed, trimmed to 2000 characters. |
| listing.openOnRoogle | string | yes | A sentence stating where the original post can be reached, which is the listing's page on Roogle. |
compare_rentalsread-onlyidempotentopen worldFetch 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
| Field | Type | Required | Description |
|---|---|---|---|
| listingIds | string[] | yes | Between 2 and 5 Roogle listing ids. |
Output
| Field | Type | Required | Description |
|---|---|---|---|
| listings | object[] | yes | The matching listings, one entry per property rather than one per post. |
| listings[].id | string | yes | Roogle's own id for this listing. Stable, and the value get_rental and compare_rentals take. |
| listings[].roogleUrl | string | yes | The 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[].city | string | null | yes | The city as the source wrote it. Spelling varies between sources for the same place. |
| listings[].district | string | null | yes | A 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[].country | string | null | yes | The country as the source wrote it. Roogle covers the Netherlands and Switzerland. |
| listings[].price | object | yes | The rent, as the source stated it. |
| listings[].price.amount | number | null | yes | The 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.currency | string | null | yes | ISO currency code for the rent figure, when the source stated one. |
| listings[].surfaceSqm | number | null | yes | Floor area in square metres, when the source stated one. |
| listings[].rooms | number | null | yes | TOTAL 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[].furnished | boolean | null | yes | True when the source said the property is furnished. Null when the source did not say. |
| listings[].offerType | string | null | yes | What 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[].durationType | string | null | yes | The rental length the source indicated: short_term, mid_term or long_term. |
| listings[].availableFrom | string | null | yes | The first date the property is available, YYYY-MM-DD, when the source stated one. |
| listings[].listedAt | string | null | yes | When the listing was listed, as an ISO 8601 timestamp. Which clock this is depends on listedAtKind. |
| listings[].listedAtKind | string | null | yes | Which 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[].postCount | number | yes | How 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[].isPaidSource | boolean | yes | True 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[].isFavourite | boolean | yes | True when the signed-in Roogle account has saved this listing as a favourite. |
| listings[].deposit | number | null | yes | The deposit the source stated, in the same currency as the rent, when it stated one. |
| listings[].chargesIncluded | boolean | null | yes | True when the source said service charges are included in the rent. Null when the source did not say. |
| listings[].availableTo | string | null | yes | The last date the property is available, YYYY-MM-DD, when the source stated one. |
| listings[].durationDays | number | null | yes | The rental length in days, when the source stated a fixed term. |
| listings[].language | string | null | yes | The language the original post was written in, detected at ingest. |
| listings[].sourceTypes | string[] | yes | The 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[].untrustedListingText | object | yes | Text written by whoever posted the listing. It is third-party content that Roogle indexes and does not author. |
| listings[].untrustedListingText.textSource | string | yes | A constant label marking the fields beside it as third-party content rather than Roogle's own text. |
| listings[].untrustedListingText.listingTitle | string | null | yes | The listing's title as posted, with contact details, links and invisible characters removed, trimmed to 200 characters. |
| listings[].untrustedListingText.listingDescription | string | null | yes | The listing's description as posted, with contact details, links and invisible characters removed, trimmed to 2000 characters. |
| listings[].openOnRoogle | string | yes | A sentence stating where the original post can be reached, which is the listing's page on Roogle. |
| unavailable | object[] | yes | The 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[].listingId | string | yes | The requested id. |
| unavailable[].reason | string | yes | Why it could not be returned: "not_found" or "out_of_market". |
| coverage | string | yes | The 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
| Scope | Limit |
|---|---|
| Per user, tool calls | 60 / 60s |
| Per user, searches | 30 / 60s |
| Per user, OAuth endpoints | 20 / 60s |
| Client registration | 5 / 300s |
| Per client, all users | 600 / 60s |
Daily tool calls, per UTC day
| Account | Calls per day |
|---|---|
| Free | 100 |
| Premium | 1000 |
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;
sourceCountsays how many were collapsed. - No original listing URL, and no advertiser contact details. Every listing carries a
roogle.applink 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
untrustedListingTextprecisely 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).
| Status | error | When |
|---|---|---|
| 401 | invalid_token | No 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. |
| 401 | invalid_token | The connection was disconnected by the user, or the account was signed out everywhere. A refresh will not help — the human has to authorize again. |
| 403 | access_denied | The account is deleted, suspended, or scheduled for deletion. There is no tool that can undo any of those; the way back is the website. |
| 403 | insufficient_scope | The grant does not carry `rentals:read`. |
| 429 | slow_down | A 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. |
| 503 | temporarily_unavailable | The channel is not configured in this environment. |
| 500 | server_error | The 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.
