Search
Maps, Places & Reviews
Local business data from Google Maps: search the map, list the businesses that match a query in a named place, read one place's reviews, and get one place's full details: its attributes (payments, service options, children…) and menu.
Endpoints#
- GET
/search/maps - GET
/search/places - GET
/search/reviews - GET
/search/place-details
Credits: 1 per request. These four endpoints also count against a tighter local rate-limit group, on top of the search group: a request must pass both (see Rate Limiting).
Maps Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Yes | Search query (1–500 chars). A place name in the query geocodes on its own, e.g. coffee shops in austin |
limit | number | No | Results (1–20, default: 20) |
page | number | No | Page number (1–100, default: 1) |
gl | string | No | Country code, max 5 chars (e.g. us). With no ll, lat or lng it also sets where the search looks (see below) |
hl | string | No | Language code, max 5 chars (e.g. en) |
ll | string | No | Viewport as a Google ll token, max 64 chars, e.g. @30.2672,-97.7431,13z |
lat | number | No | Latitude to steer results toward (-90 to 90) |
lng | number | No | Longitude to steer results toward (-180 to 180) |
zoom | number | No | Map zoom (1–21) |
cache | boolean | No | false skips the cached copy and looks the places up now. Same credits (default: true) |
Maps Response#
{ "searchParameters": { "q": "coffee shops in austin", "type": "maps", "gl": "us", "hl": "en", "num": 20, "page": 1, "ll": "@39.5,-98.35,5z" }, "lite": false, "page": 1, "totalResults": 20, "places": [ { "position": 1, "title": "Example Coffee Roasters", "placeId": "ChIJ7cv00Dm1RIYRx5mHcIYlGmE", "cid": "0x8644b5a4e3d8f52b:0x5e1c2c3a4b5d6e7f", "featureId": "/g/11c5k8tq1n", "category": "Coffee shop", "categories": ["Coffee shop", "Cafe"], "rating": 4.7, "reviews": 1834, "price": "$$", "address": "123 Congress Ave, Austin, TX 78701", "latitude": 30.2672, "longitude": -97.7431, "website": "https://coffee.example.com/", "domain": "coffee.example.com", "phone": "(512) 555-0100", "timezone": "America/Chicago", "description": "Small-batch roaster with a patio.", "openState": "Open · Closes 6 PM", "openNow": true, "businessStatus": "OPERATIONAL", "hours": null, "openingDate": null, "openingDateText": null, "attributes": { "service_options": { "serves_dine_in": true, "has_takeout": true, "has_delivery": false }, "payments": { "requires_cash_only": false, "pay_credit_card": true, "pay_mobile_nfc": true } }, "thumbnailUrl": "https://lh5.googleusercontent.com/...", "reviewsLink": "https://www.google.com/maps/...", "bookingLink": null, "bookingLinks": null } ], "source": "searlo.tech(engine)", "credits": 1}lite: truemeans Google served its reduced record. Since October 2026 that is what Google Maps shows every signed-out visitor, so expectlite: trueon maps and places: no price level, and accessibility as the only attribute group (the sample above shows the full record). A lite answer is never cached.businessStatussays whether the place still trades:OPERATIONAL,CLOSED_TEMPORARILYorCLOSED_PERMANENTLY, as the Places API names them, ornullwhen Google's record does not say. It is never guessed from missing data.openNowistrueorfalsefor whether the place is open right now, ornullwhen the record does not say.openStateis Google's own state line, in the language ofhl("Abierto · Cierre: 21:30"withhl=es). A place that has closed (businessStatusCLOSED_TEMPORARILYorCLOSED_PERMANENTLY) has both set tonull.- With no
ll,latorlng,glalso sets where the search looks: the main city of that country, for 51 countries (gl=eswithfarmaciaanswers from Spain, not the US). A place named inq(hoteles en tokio) decides for itself.gl=us, nogl, and a country without a default area keep the US-wide default.searchParameters.llechoes the area used. - In a maps result,
cidis the0x…:0x…place id,featureIdis a Knowledge Graph id (/g/…) andplaceIdis the Google Place ID (ChIJ…). bookingLinkis the single link to send a user to when the place takes bookings or orders;bookingLinksholds the full set.attributesis Google's "About" list for the place, grouped as Google groups it (service_options,payments,children,parking,offerings…) and keyed by Google's own ids, the same in every language. A yes/no attribute istrueorfalse(attributes.payments.requires_cash_onlyistrueon a cash-only place,falsewhere Google says it is not), a value attribute is a string (price_level), a list one an array (pay_credit_card_types_accepted). It isnullwhen Google lists none, and aliteanswer carries accessibility only.- A place listed before it opens has
openingDateText, Google's text in yourhl("Opens 1 Nov"), andopeningDate, the date read from it ("2026-11-01"). Both arenullon a place that is open, and so isopenStateon one that is not open yet./search/placescarries the same two fields. - A maps query that finds nothing answers
200withplaces: [], and is not charged.
Places Parameters#
GET /search/places lists the businesses that match a query in a place you name, instead of a map viewport.
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Yes | What to look for (1–500 chars), e.g. dentist |
location | string | No | Where to search, max 200 chars, e.g. Austin, Texas |
limit | number | No | Results (1–20, default: 20) |
page | number | No | Page number (1–100, default: 1) |
gl | string | No | Country code, max 5 chars |
hl | string | No | Language code, max 5 chars |
ll | string | No | Viewport token, as on maps |
lat | number | No | Latitude (-90 to 90) |
lng | number | No | Longitude (-180 to 180) |
cache | boolean | No | false skips the cached copy and looks the places up now. Same credits (default: true) |
Places Response#
{ "searchParameters": { "q": "dentist", "type": "places", "location": "Austin, Texas", "gl": "us", "hl": "en", "num": 20, "page": 1 }, "lite": false, "page": 1, "totalResults": 20, "places": [ { "position": 1, "title": "Example Family Dental", "address": "456 Lamar Blvd, Austin, TX 78703", "latitude": 30.2849, "longitude": -97.7545, "rating": 4.9, "ratingCount": 412, "category": "Dentist", "categories": ["Dentist", "Cosmetic dentist"], "priceLevel": null, "phoneNumber": "(512) 555-0199", "website": "https://dental.example.com/", "domain": "dental.example.com", "openState": "Closed · Opens 8 AM", "openNow": false, "businessStatus": "OPERATIONAL", "hours": null, "openingDate": null, "openingDateText": null, "attributes": { "payments": { "pay_credit_card": true, "requires_cash_only": false } }, "thumbnailUrl": "https://lh5.googleusercontent.com/...", "cid": "0x8644b5a4e3d8f52b:0x2f1e3d4c5b6a7980", "featureId": "0x8644b5a4e3d8f52b:0x2f1e3d4c5b6a7980", "knowledgeGraphId": "/g/1tf6yx2q", "placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4" } ], "source": "searlo.tech(google_places)", "credits": 1}A places query that finds nothing returns 200 with places: [], and is not charged. openState, openNow, businessStatus and lite mean the same as on maps.
Reviews Parameters#
GET /search/reviews returns the reviews for one place. Identify it one of four ways. placeId, featureId and cid are faster than q, which costs a place lookup first.
| Parameter | Type | Required | Description |
|---|---|---|---|
placeId | string | One of four | A Google Place ID (ChIJ…), as on a maps result or from the Places API |
featureId | string | One of four | The 0x…:0x… place id: featureId on a places result, or cid on a maps result |
cid | string | One of four | The decimal place id from a Google Maps share link (digits only, max 32). Not the 0x… value |
q | string | One of four | A place name to look up (1–500 chars); the top match is used |
limit | number | No | Reviews to return (1–50, default: 10). A call returns the first page Google serves, about eight reviews: limit can only lower that |
gl | string | No | Country code, max 5 chars |
hl | string | No | Language code, max 5 chars. Sets the language of relativeDate |
Reviews Response#
{ "searchParameters": { "type": "reviews", "featureId": "0x8644b5a4e3d8f52b:0x2f1e3d4c5b6a7980", "hl": "en", "gl": "us", "num": 10 }, "place": { "title": "Example Family Dental", "address": "456 Lamar Blvd, Austin, TX 78703", "featureId": "0x8644b5a4e3d8f52b:0x2f1e3d4c5b6a7980", "rating": 4.9, "reviewCount": 412 }, "totalResults": 8, "hasMore": true, "hasNextPage": true, "nextPageToken": "CAESBkVnSUlDQQ", "reviews": [ { "position": 1, "reviewId": "ChZDSUhNMG9nS0VJQ0FnSUNf", "rating": 5, "text": "Friendly staff and no wait.", "date": "2026-09-14T16:20:00.000Z", "timestamp": 1789402800, "relativeDate": "2 weeks ago", "language": "en", "author": "Jordan P.", "authorProfile": "https://www.google.com/maps/contrib/...", "authorPhoto": "https://lh3.googleusercontent.com/...", "authorId": "104512345678901234567", "authorReviewCount": 37, "isLocalGuide": true, "response": { "text": "Thank you, Jordan!", "relativeTime": "a week ago", "date": "2026-09-21T09:00:00.000Z", "language": "en" } } ], "source": "searlo.tech(google_reviews)", "credits": 1}- Each call returns the first page of reviews Google serves with the place, about eight, in any language:
hlsets the language ofrelativeDate, andlanguageis the one each review was written in. hasMore(alsohasNextPage) istruewhen the place has more reviews than that page. Later pages cannot be fetched yet:nextPageToken, when present, is Google's token for them, and no parameter takes it.place.reviewCountis the lifetime count.dateis the ISO time,timestampthe same in Unix seconds andrelativeDateGoogle's own text.responseis present only when the owner replied:text,relativeTime,dateandlanguage.- A place with no reviews answers
200withreviews: [], and is not charged. A place Google reports reviews for but sent without them answers503REVIEWS_WITHHELD(retryable: true), also not charged: retry the request.
Place Details Parameters#
GET /search/place-details returns everything Google's place panel holds for one place: every field a maps result has, plus the full attribute list and the menu. Identify the place as on reviews; a placeId costs no lookup.
| Parameter | Type | Required | Description |
|---|---|---|---|
placeId | string | One of four | A Google Place ID (ChIJ…) |
featureId | string | One of four | The 0x…:0x… place id |
cid | string | One of four | The decimal place id from a Google Maps share link |
q | string | One of four | A place name to look up (1–500 chars); the top match is used |
gl | string | No | Country code, max 5 chars |
hl | string | No | Language code, max 5 chars; sets the language of labels, descriptions and the menu |
menu | boolean | No | false skips the menu lookup for a place that serves food (a faster answer when you do not need the menu) |
Place Details Response#
{ "searchParameters": { "type": "place-details", "placeId": "ChIJV8rJWPR1nkcRfAVGOrZIeVs", "featureId": "0x479e75f458c9ca57:0x5b7948b63a46057c", "hl": "en", "gl": "de" }, "lite": true, "place": { "title": "Augustiner Klosterwirt", "placeId": "ChIJV8rJWPR1nkcRfAVGOrZIeVs", "cid": "0x479e75f458c9ca57:0x5b7948b63a46057c", "featureId": "/g/1ptx9q8dt", "category": "Bavarian restaurant", "rating": 4.5, "reviews": 14740, "address": "Augustinerstraße 1, 80331 München-Altstadt-Lehel, Germany", "website": "http://augustiner-klosterwirt.de/", "phone": "+498955054466", "openState": "Open · Closes 12 AM", "openNow": true, "businessStatus": "OPERATIONAL", "hours": [{ "day": "Friday", "hours": ["9:30 AM–12 AM"] }], "attributes": { "accessibility": { "has_wheelchair_accessible_entrance": true, "has_wheelchair_accessible_restroom": true } }, "menu": { "url": "https://augustiner-klosterwirt.de/index.php/speisen-getraenke/speisekarte", "sections": [ { "name": "Soups", "items": [{ "name": "Bavarian Liver Dumpling Soup", "description": "With fresh parsley", "price": "€7.50" }] } ], "photos": [] }, "menuStatus": "found" }, "source": "searlo.tech(google_place)", "credits": 1}placecarries every field of a maps result (openState,openNow,businessStatus,openingDate,bookingLinks…) with the same meaning.menuis the menu Google holds for the place:url(the restaurant's own menu page) andsectionsof items with descriptions and prices as Google shows them, in the languagehlnames. Since October 2026 Google Maps shows signed-out visitors no menu, so for a place that serves food the menu is read from the place's Google Search panel instead.photosis empty for now. Any part Google does not have is empty, andmenuisnullwhen it has none.menuStatussays how the menu lookup went:found;none(Google holds no menu for the place);pending(still being read: ask again in a few minutes and it is there, which is how a large batch gets its menus, on a second pass);unavailable(the lookup failed: retry later). It is absent for a place that serves no food. A menu is kept for 7 days, so asking again costs no wait.lite: truemeans Google answered its reduced place record: since October 2026 that is what Google Maps shows every signed-out visitor, with accessibility as the only attribute group and no price level. A lite answer is never cached.- A
placeIdthat names no business (an address or plus-code id) or aqthat matches nothing answers400, an id Google does not know404, both witherrorCode: "PLACE_NOT_FOUND"and not charged.
Examples
# Map searchcurl "https://api.searlo.tech/api/v1/search/maps?q=coffee+shops+in+austin&limit=10" \ -H "x-api-key: sk_your_api_key" # Looked up now instead of from the cache; gl=es places the search in Spaincurl "https://api.searlo.tech/api/v1/search/maps?q=farmacia&gl=es&hl=es&cache=false" \ -H "x-api-key: sk_your_api_key" # Businesses in a named placecurl "https://api.searlo.tech/api/v1/search/places?q=dentist&location=Austin,+Texas" \ -H "x-api-key: sk_your_api_key" # Everything about one place, by its Google Place IDcurl "https://api.searlo.tech/api/v1/search/place-details?placeId=ChIJd2ts_zTbmUcRqrBZ97_BxpA&hl=en" \ -H "x-api-key: sk_your_api_key" # Reviews for one placecurl "https://api.searlo.tech/api/v1/search/reviews?featureId=0x8644b5a4e3d8f52b:0x2f1e3d4c5b6a7980&limit=20" \ -H "x-api-key: sk_your_api_key"const API = "https://api.searlo.tech/api/v1";const headers = { "x-api-key": process.env.SEARLO_API_KEY }; // 1. Find the businessesconst placesParams = new URLSearchParams({ q: "dentist", location: "Austin, Texas" });const places = await (await fetch(`${API}/search/places?${placesParams}`, { headers })).json(); // 2. Read the reviews of the first one that carries a featureIdconst place = places.places.find(p => p.featureId);if (!place) throw new Error("No result carried a featureId");const reviewParams = new URLSearchParams({ featureId: place.featureId, limit: "20" });const reviews = await (await fetch(`${API}/search/reviews?${reviewParams}`, { headers })).json(); console.log(reviews.place.title, reviews.place.rating, reviews.place.reviewCount);reviews.reviews.forEach(r => console.log(r.rating, r.relativeDate, r.text.slice(0, 80)));import osimport requests res = requests.get( "https://api.searlo.tech/api/v1/search/maps", params={"q": "coffee shops in austin", "limit": 10, "gl": "us"}, headers={"x-api-key": os.environ["SEARLO_API_KEY"]},)data = res.json()if data.get("lite"): print("Reduced tier served: review counts, price and hours may be missing")for p in data["places"]: if p["businessStatus"] in ("CLOSED_TEMPORARILY", "CLOSED_PERMANENTLY"): continue # the place has closed print(p["position"], p["title"], p["rating"], p["reviews"], p["address"], p["openNow"])Stuck? Write to [email protected], or check live status.