> Searlo API Docs — Maps, Places & Reviews
> Source: https://searlo.tech/docs#local-search
> Full docs: https://searlo.tech/docs/full.md

# 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](#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

```json
{
  "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: true` means Google served its reduced record. Since October 2026 that is what Google Maps shows every signed-out visitor, so expect `lite: true` on 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.
- `businessStatus` says whether the place still trades: `OPERATIONAL`, `CLOSED_TEMPORARILY` or `CLOSED_PERMANENTLY`, as the Places API names them, or `null` when Google's record does not say. It is never guessed from missing data.
- `openNow` is `true` or `false` for whether the place is open right now, or `null` when the record does not say. `openState` is Google's own state line, in the language of `hl` (`"Abierto · Cierre: 21:30"` with `hl=es`). A place that has closed (`businessStatus` `CLOSED_TEMPORARILY` or `CLOSED_PERMANENTLY`) has both set to `null`.
- With no `ll`, `lat` or `lng`, `gl` also sets where the search looks: the main city of that country, for 51 countries (`gl=es` with `farmacia` answers from Spain, not the US). A place named in `q` (`hoteles en tokio`) decides for itself. `gl=us`, no `gl`, and a country without a default area keep the US-wide default. `searchParameters.ll` echoes the area used.
- In a maps result, `cid` is the `0x…:0x…` place id, `featureId` is a Knowledge Graph id (`/g/…`) and `placeId` is the Google Place ID (`ChIJ…`).
- `bookingLink` is the single link to send a user to when the place takes bookings or orders; `bookingLinks` holds the full set.
- `attributes` is 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 is `true` or `false` (`attributes.payments.requires_cash_only` is `true` on a cash-only place, `false` where Google says it is not), a value attribute is a string (`price_level`), a list one an array (`pay_credit_card_types_accepted`). It is `null` when Google lists none, and a `lite` answer carries accessibility only.
- A place listed before it opens has `openingDateText`, Google's text in your `hl` (`"Opens 1 Nov"`), and `openingDate`, the date read from it (`"2026-11-01"`). Both are `null` on a place that is open, and so is `openState` on one that is not open yet. `/search/places` carries the same two fields.
- A maps query that finds nothing answers `200` with `places: []`, 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

```json
{
  "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

```json
{
  "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: `hl` sets the language of `relativeDate`, and `language` is the one each review was written in.
- `hasMore` (also `hasNextPage`) is `true` when 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.reviewCount` is the lifetime count.
- `date` is the ISO time, `timestamp` the same in Unix seconds and `relativeDate` Google's own text.
- `response` is present only when the owner replied: `text`, `relativeTime`, `date` and `language`.
- A place with no reviews answers `200` with `reviews: []`, and is not charged. A place Google reports reviews for but sent without them answers `503` `REVIEWS_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

```json
{
  "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
}
```

- `place` carries every field of a maps result (`openState`, `openNow`, `businessStatus`, `openingDate`, `bookingLinks`…) with the same meaning.
- `menu` is the menu Google holds for the place: `url` (the restaurant's own menu page) and `sections` of items with descriptions and prices as Google shows them, in the language `hl` names. 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. `photos` is empty for now. Any part Google does not have is empty, and `menu` is `null` when it has none.
- `menuStatus` says 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: true` means 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 `placeId` that names no business (an address or plus-code id) or a `q` that matches nothing answers `400`, an id Google does not know `404`, both with `errorCode: "PLACE_NOT_FOUND"` and not charged.

## Code Examples

### cURL

```bash
# Map search
curl "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 Spain
curl "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 place
curl "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 ID
curl "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 place
curl "https://api.searlo.tech/api/v1/search/reviews?featureId=0x8644b5a4e3d8f52b:0x2f1e3d4c5b6a7980&limit=20" \
  -H "x-api-key: sk_your_api_key"
```

### JavaScript

```javascript
const API = "https://api.searlo.tech/api/v1";
const headers = { "x-api-key": process.env.SEARLO_API_KEY };

// 1. Find the businesses
const 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 featureId
const 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)));
```

### Python

```python
import os
import 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"])
```


---
*From https://searlo.tech/docs — see full docs at https://searlo.tech/docs/full.md*