Google Maps data, one request away
A ready-to-use REST API for Google Maps. Search a point for its local businesses, resolve any place by its place_id into its full record, pull the complete review set, or rank a business across a local search grid, all matched by stable place id, billed only for successful calls.
Introduction
The Hexscraper API is a synchronous request/response service: you call an endpoint, you get the data back in the same HTTP response, no queue or webhook to manage. Every business is addressed by its Google place_id (a CID-backed ChIJ… id), so results are never confused between same-name look-alikes.
All responses are JSON. The base URL is https://api.hexscraper.com.
Authentication
Authenticate every request with an API key in the Authorization header as a Bearer token. Keys are issued per account, so keep them server-side and never ship one to a browser.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxA missing or invalid key returns 401.
Billing & credits
Usage is metered in credits. Billing is reserve-then-refund: a call reserves its credits up front and refunds anything unused, so you only pay for successful results. A 4xx, a 503, or an upstream failure is never charged.
| Endpoint | Cost |
|---|---|
| GET /v1/search | 1 credit per 100 businesses returned (minimum 1); empty searches are free |
| GET /v1/places/{id} | 1 credit per lookup |
| GET /v1/places/{id}/reviews | 1 credit per 10 reviews returned (min 1) |
| GET /v1/places/{id}/photos | 1 credit per 100 photos returned (minimum 1) |
| GET /v1/visibility | grid_size² credits (e.g. a 5×5 grid = 25) |
A cache hit still bills (it saves you the latency, not the credit). Running out of credits returns 402.
Rate limits
Limits are per account, over a fixed one-minute window: 240/min for point search, place lookups and the visibility grid, 180/min for reviews (each reviews call can page a lot of data). Exceeding a limit returns 429 with a Retry-After header, and is never billed.
Idempotency
Send an Idempotency-Key header on any request. A retry carrying the same key replays the stored response without re-charging or re-scraping; a concurrent duplicate gets 409 while the first is still running.
Idempotency-Key: 9f1c2b7a-restaurant-batch-042Errors
Errors use standard HTTP status codes and a JSON body { "detail": "…" }. A 503 means the data was temporarily unavailable (not that the place doesn't exist). Retry after the Retry-After delay; it is never billed.
| Status | Meaning |
|---|---|
| 400 | Malformed place_id (not a CID-backed ChIJ… id) or invalid parameters. |
| 401 | Missing or invalid API key. |
| 402 | Out of credits. Top up to continue. |
| 409 | A request with the same Idempotency-Key is already in progress. |
| 429 | Rate limit exceeded. Honour the Retry-After header. Not billed. |
| 503 | Temporarily unavailable (upstream/farm). Retry after Retry-After. Not billed. |
Point search
1 credit / 100 businesses/v1/searchQuery parameters
| Field | Type | Description |
|---|---|---|
| keywordrequired | string | The search term, e.g. "tapas" or "dentist". URL-encode spaces and accents (a space becomes %20). |
| latrequired | number | Latitude of the search centre (−90 to 90). |
| lonrequired | number | Longitude of the search centre (−180 to 180). |
| limit | integer | How many businesses to return, 1 to 400 (default 100). The list Google keeps for a search usually ends before 400: 170 to 340 in a city centre. More takes longer: about 1.2 s for 100 and 2 to 4 s for 400. |
| hl | string | Google Maps host language for the response, e.g. "es" or "pt-BR". Categories, attributes and price formatting come back in that language, and they are Google's own strings, not a translation. Defaults to English. |
Request
curl "https://api.hexscraper.com/v1/search?keyword=tapas&lat=36.3707&lon=-6.1809" \
-H "Authorization: Bearer $HEXSCRAPER_API_KEY"Response
{
"keyword": "tapas",
"latitude": 36.3707,
"longitude": -6.1809,
"count": 20,
"results": [
{
"place_id": "ChIJx3-pvNszDA0R837NptBLjM4",
"name": "Café bar la Almadraba",
"rating": 4.6,
"reviews_count": 1071,
"price_level": "€10-20",
"phone": null,
"website": null,
"address": "Avenida la Barrosa, 11130 Chiclana, Cádiz",
"city": "Chiclana",
"latitude": 36.3707792,
"longitude": -6.180956,
"categories": ["Restaurante", "Bar", "Pub"],
"primary_category": "Restaurante",
"business_status": "Open"
}
]
}An empty result (no businesses at that point) returns count: 0 and is not billed. For richer fields on a single business (amenities, popular times, the full review set), feed a result's place_id into the endpoints below.
Place details
1 credit/v1/places/{place_id}Path parameters
| Field | Type | Description |
|---|---|---|
| place_idrequired | string | A CID-backed Google place_id (ChIJ…). |
Query parameters
| Field | Type | Description |
|---|---|---|
| hl | string | Google Maps host language for the response, e.g. "es" or "pt-BR". Defaults to English. |
| fresh | boolean | true to skip the cache and read Google now (the fresh answer is still cached for the next call). |
Request
curl "https://api.hexscraper.com/v1/places/ChIJx3-pvNszDA0R837NptBLjM4" \
-H "Authorization: Bearer $HEXSCRAPER_API_KEY"Response
{
"place_id": "ChIJx3-pvNszDA0R837NptBLjM4",
"name": "Café bar la Almadraba",
"rating": 4.6,
"reviews_count": 1071,
"price_level": "€10-20",
"website": null,
"phone": null,
"address": "Avenida la Barrosa, 11130 Chiclana, Cádiz",
"street": "Avenida la Barrosa",
"district": null,
"city": "Chiclana",
"state": "Cádiz",
"postal_code": "11130",
"country_code": "ES",
"plus_code": "8C8M9RC9+8J",
"latitude": 36.3707792,
"longitude": -6.180956,
"categories": ["Restaurante", "Bar", "Pub"],
"primary_category": "Restaurante",
"business_status": "Open",
"num_photos": 775,
"is_claimed": true,
"owner_name": "Café bar la Almadraba",
"time_spent": "People typically spend 45 min to 1.5 hr here",
"reviews_tags": [
{ "keyword": "tapas", "count": 83 },
{ "keyword": "fried fish", "count": 12 }
],
"popular_times": { "Friday": [[12, 0], [13, 54], [20, 88], [21, 71]] },
"people_also_search": [
{ "name": "EL ARBOL TAPAS", "cid": "0x0:0x9f69...", "rating": 4.7, "reviews": 6988 }
],
"rating_summary": [
{ "stars": 5, "amount": 784 },
{ "stars": 4, "amount": 199 }
],
"reviews": [
{
"review_id": "ChZ...",
"author_name": "A VG",
"review_rating": 2,
"review_text": "Overpriced for what it is.",
"review_datetime_utc": "2025-06-29T20:58:02+00:00"
}
],
"business_description": "Pollos asados y pescado frito en casa, también para llevar.",
"popular_dishes": [{ "name": "Medio Pollo Con Patatas", "photo": "https://lh3..." }],
"menu": [
{
"section": "Pizzas y pasta",
"items": [
{ "name": "Pizza Margarita", "description": "Base fina…", "price": "€10.50", "photo": "https://lh3..." }
]
}
],
"photo_categories": ["Del propietario", "Carta", "Comida y bebida", "Ambiente"],
"posts": [
{ "text": "QUESO GORGONZOLA: origen y sabor…", "date": "Aug 4, 2026", "timestamp": 1785848535, "image": "https://lh3…", "cta_label": "Book", "cta_url": "https://…" }
],
"action_links": [
{ "url": "https://www.google.com/maps/reserve/v/dine/c/…", "label": "Reservar mesa", "kind": "reservation" },
{ "url": "https://glovoapp.com/es/es/chiclana/…", "label": "glovoapp.com", "kind": "order" }
],
"attributes": [
{
"key": "service_options",
"label": "Opciones de servicio",
"items": [
{ "key": "has_takeout", "label": "Para llevar", "available": true, "text": "Ofrece comida para llevar" },
{ "key": "has_delivery", "label": "Entrega a domicilio", "available": false, "text": "No ofrece servicio de entrega a domicilio" }
]
}
]
}Reviews
1 credit / 10 reviews/v1/places/{place_id}/reviewsPath parameters
| Field | Type | Description |
|---|---|---|
| place_idrequired | string | A CID-backed Google place_id (ChIJ…). |
Query parameters
| Field | Type | Description |
|---|---|---|
| limit | integer | Max reviews to return. Default 10, no hard cap; bounded by the place's actual review count and your credit balance. |
| sort | string | Order: relevant (Google's, the default), newest, highest or lowest. |
| page_token | string | The next_page_token of an earlier response (same sort and hl) to carry on reading from there. |
| hl | string | Google Maps host language for the response, e.g. "es" or "pt-BR". Categories, attributes and price formatting come back in that language, and they are Google's own strings, not a translation. Defaults to English. |
| fresh | boolean | true to skip the cache and read Google now (the fresh answer is still cached for the next call). |
Request
curl "https://api.hexscraper.com/v1/places/ChIJTzciJkUxDA0RtK7jOAlZTKo/reviews?limit=50" \
-H "Authorization: Bearer $HEXSCRAPER_API_KEY"Response
{
"place_id": "ChIJTzciJkUxDA0RtK7jOAlZTKo",
"count": 50,
"reviews": [
{
"review_id": "ChdD...",
"author_id": "1094...",
"author_name": "Pilar Pavón",
"author_link": "https://www.google.com/maps/contrib/1094...",
"review_rating": 5,
"review_text": "Absolutely phenomenal food. The garlic prawns...",
"review_timestamp": 1751803869,
"review_datetime_utc": "2025-07-06T12:11:09+00:00",
"relative_date": "2 months ago"
}
]
}Photos
1 credit / 100 photos/v1/places/{place_id}/photosPath parameters
| Field | Type | Description |
|---|---|---|
| place_idrequired | string | A CID-backed Google place_id (ChIJ…). |
Query parameters
| Field | Type | Description |
|---|---|---|
| category | string | all (default), latest, owner, menu, food, vibe, videos, street_view, or a dish tab's key as it comes in categories ("tab:…"). |
| limit | integer | Max photos to return, 1 to 500. Default 100. |
| page_token | string | The next_page_token of an earlier response (same category and hl) for the next photos; null when there are no more. |
| hl | string | Google Maps host language for the response, e.g. "es" or "pt-BR". Defaults to English. |
| fresh | boolean | true to skip the cache and read Google now (the fresh answer is still cached for the next call). |
Request
curl "https://api.hexscraper.com/v1/places/ChIJx3-pvNszDA0R837NptBLjM4/photos?category=owner&limit=100" \
-H "Authorization: Bearer $HEXSCRAPER_API_KEY"Response
{
"place_id": "ChIJx3-pvNszDA0R837NptBLjM4",
"category": "owner",
"total": 53,
"count": 47,
"photos": [
{
"photo_id": "CIHM0ogKEICAgIDhnv2FTg",
"kind": "photo",
"url": "https://lh3.googleusercontent.com/gps-cs-s/…=w2945-h2209-k-no",
"width": 4000,
"height": 3000,
"author_name": "Café bar la Almadraba",
"author_link": "https://maps.google.com/maps/contrib/1028…",
"uploaded_on": "2021-09-05"
}
],
"categories": [
{ "key": "all", "label": "Todas" },
{ "key": "menu", "label": "Carta" },
{ "key": "food", "label": "Comida y bebida" },
{ "key": "tab:CgwqCC9tLzAxNTk5MAs", "label": "Cerveza" },
{ "key": "owner", "label": "Del propietario" }
],
"next_page_token": null
}The total is Google's count and usually runs a little above the photos that can be listed (Google counts some it does not show). For all it is null: the place's photo count is the record's num_photos. A business that never uploaded returns an empty owner category and leaves it out of categories. URLs take a size: replace the trailing =w…-h…-k-no with, e.g., =w1200-h1200-k-no.
Visibility grid
grid_size² credits/v1/visibilityQuery parameters
| Field | Type | Description |
|---|---|---|
| place_idrequired | string | The business to rank (a CID-backed ChIJ… id). |
| keywordrequired | string | The search term to rank for, e.g. "tapas" (URL-encode special characters). |
| grid_size | integer | Odd number 3–7. Default 5 (a 5×5 grid = 25 searches). |
| radius_m | integer | Grid half-width in metres, 1–50000. Default 1000. The response echoes it as radius_km. |
| searcher | string | Where the searcher stands: cell (the default) places them at each cell, like someone with a phone on that spot, which is what geo-grid tools measure; ocean places them nowhere and ranks the map around the point. The response says which in searcher_location. |
| hl | string | Google Maps host language for the response, e.g. "es" or "pt-BR". Defaults to English. |
Request
curl "https://api.hexscraper.com/v1/visibility?place_id=ChIJx3-pvNszDA0R837NptBLjM4&keyword=tapas&grid_size=5&radius_m=1000" \
-H "Authorization: Bearer $HEXSCRAPER_API_KEY"Response
{
"keyword": "tapas",
"target_place_id": "ChIJx3-pvNszDA0R837NptBLjM4",
"target_name": "Café bar la Almadraba",
"center_lat": 36.3707792,
"center_lon": -6.180956,
"grid_size": 5,
"radius_km": 1.0,
"total_points": 25,
"found_in": 18,
"average_rank": 3.4,
"grid": [
{ "row": 0, "col": 0, "lat": 36.3752, "lon": -6.1849,
"rank": 2, "top_result": "Café bar la Almadraba", "total_results": 100,
"top_places": [
{ "rank": 1, "place_id": "0x0:0x9f69...", "name": "EL ARBOL TAPAS",
"rating": 4.7, "reviews": 6988, "photo": "https://lh3.googleusercontent.com/…" },
{ "rank": 2, "place_id": "ChIJx3-pvNszDA0R837NptBLjM4", "name": "Café bar la Almadraba",
"rating": 4.6, "reviews": 1204 }
] }
],
"competitors": [
{ "place_id": "0x0:0x9f69...", "name": "EL ARBOL TAPAS",
"avg_total_rank": 2.3, "appearances": 25, "avg_rank": 2.3, "best_rank": 1, "top3_count": 20,
"total_reviews": 6988, "avg_rating": 4.7, "is_target": false }
],
"top_rankings": [ { "name": "Café bar la Almadraba", "count": 18 } ],
"degraded": false,
"searcher_location": "cell"
}If the results look degraded (Google blanked review counts on a burned session), the response sets degraded: true. The data may be incomplete and is not cached.
Ready to build?
Get an API key and your first credits to start pulling Google Maps data.
