API Platform
Business Data API
Google business listings — find businesses by category, name and location, read one business's full profile, and pull its reviews with ratings, dates and owner replies.
Search Google's business listings, millions of them in Turkey alone, by category, name and distance. Read a business's full profile: rating breakdown, opening hours, description and attributes. Pull its reviews with ratings, dates, authors and owner replies. One endpoint serves three reports, selected with the type parameter.
Reviews always run as a task
This endpoint runs every request as a task. search and profile usually answer within the default 60-second wait with a normal 200. reviews take one to three minutes to collect, so a reviews request almost always returns 202 with a task_id. Poll GET /v1/tasks/{task_id} until the result is ready; there is a loop below. Every response carries the task id in the X-Task-Id header. Retry safely with an Idempotency-Key header: never re-send a request that returned 202, because a second request is a second charge.
Authentication
| Header | Type | Description |
|---|---|---|
SEMUST-API-USER | string | Your API user's username |
SEMUST-API-PASSWORD | string | Your API user's password |
Accept-Language | string | Optional. Send tr to receive error messages in Turkish |
Create API credentials from the API Access page.
SEMUST-API-USER: your_username
SEMUST-API-PASSWORD: your_password
Content-Type: application/jsonReport Types
type | Returns | Price |
|---|---|---|
search (default) | Businesses matching categories, a name and/or a circle on the map, with rating, review count, contact details and address | $0.0132 + $0.000396 per business returned |
profile | One business's full profile: everything search returns plus description, opening hours, attributes and related businesses | $0.00594 |
reviews | One business's reviews: text, rating, date, author and owner reply | $0.000825 per 10 reviews asked for |
search returns place_id and cid for every business. Pass either one to profile or reviews to address that exact business. A name is searched for instead, and the best match is used.
{
"type": "search",
"categories": ["cafe"],
"latitude": 40.9906,
"longitude": 29.0270,
"radius_km": 1,
"limit": 20
}{
"type": "profile",
"place_id": "ChIJQ3x3p-e5yhQRCzOLzD3Pdkw"
}{
"type": "reviews",
"cid": "5509819058466009867",
"sort": "newest",
"limit": 50
}Request Parameters
A parameter that does not apply to the chosen type is rejected rather than ignored, and a misspelled one is named in the error with a suggestion. A JSON null is treated as "not sent".
Body: search
categoriesstring[]1–10 category ids, such as cafe, dentist or car_repair. An unknown id is rejected with a suggestion (cafee → cafe). GET /v1/business-data/categories?q=… searches the 5,316 ids.
namestringBusinesses whose name contains this text, case-insensitively, up to 200 characters. A Turkish I or ı matches both readings, so KADIKÖY also finds Kadıköy.
latitude, longitude, radius_kmnumber, number, integerA circle on the map: centre in degrees, radius in whole kilometres, 1–500. Send all three or none.
countrystringdefault: TRTwo-letter country code. Without a location it defaults to TR. With a location there is no country filter unless you send one.
min_ratingnumberOnly businesses rated at least this, 1–5.
min_reviewsintegerOnly businesses with at least this many reviews.
claimedbooleantrue: only businesses whose owner has claimed the listing. false: only unclaimed ones.
sortstringdefault: reviewsreviews (review count) or rating.
directionstringdefault: descdesc or asc.
limitintegerdefault: 100Businesses per page, 1–1000.
pageintegerdefault: 1Page number. page × limit can be at most 20,000.
Send at least one of categories, name or a location. Filters alone are not a search.
Body: profile and reviews
place_idstringThe business's place id, from search (ChIJQ3x3p-e5yhQRCzOLzD3Pdkw).
cidstringThe business's numeric id, from search, as a string ("5509819058466009867").
namestringThe business name, searched for. Adding the city helps ("Galata Kulesi İstanbul").
countrystringdefault: TRTwo-letter country code: where name is searched for.
languagestringThe language of category names, and the language reviews are translated into. Defaults to tr for TR and en elsewhere.
sortstringdefault: relevantreviews only: relevant, newest, highest_rating or lowest_rating.
limitintegerdefault: 10reviews only. Reviews to fetch, 1–1000, rounded up to a multiple of 10 (25 becomes 30). You are billed for the rounded number, and you get up to that many.
Send exactly one of place_id, cid and name.
Body: every type
typestringdefault: searchsearch, profile or reviews.
idempotency_keystringOptional, up to 128 characters. Repeating a request with the same key returns the same task: its 202 while it runs, its stored result once it finishes. It is charged once. The key is reserved until 72 hours after the task finishes. It does the same job as the Idempotency-Key header; if you send both, this field wins.
Query
waitintegerdefault: 60Seconds to wait for the result, 0–80. If the request is still running after that, the response is 202 with a task_id. 0 answers 202 at once.
Headers
Idempotency-KeystringOptional. Works like idempotency_key above; if you send both, the body field wins.
Two free lookups:
GET /v1/business-data/categories?q=…lists the category idssearchaccepts, largest first, filtered by a case-insensitive substring. Spaces match underscores.GET /v1/business-data/marketslists every supported country and language.
curl -X POST https://data.semust.com/v1/business-data \
-H "SEMUST-API-USER: your_username" \
-H "SEMUST-API-PASSWORD: your_password" \
-H "Content-Type: application/json" \
-d '{"type": "search", "categories": ["cafe"], "latitude": 40.9906, "longitude": 29.0270, "radius_km": 1, "limit": 20}'import time
import requests
HEADERS = {
"SEMUST-API-USER": "your_username",
"SEMUST-API-PASSWORD": "your_password",
}
response = requests.post(
"https://data.semust.com/v1/business-data",
headers=HEADERS,
json={"type": "reviews", "cid": "5509819058466009867", "sort": "newest", "limit": 50},
timeout=90, # longer than ?wait= (60 seconds by default)
)
data = response.json()
# Reviews take one to three minutes: expect 202 and poll.
# Never re-POST while waiting; that would be a second charge.
while response.status_code == 202:
time.sleep(int(response.headers.get("Retry-After", data.get("retry_after", 5))))
response = requests.get(data["poll_url"], headers=HEADERS, timeout=30)
data = response.json()
if response.status_code != 200:
raise SystemExit(f"{data['code']}: {data['error']} {data.get('hint', '')}")
print(data["business"]["name"], data["business"]["rating"])
for review in data["items"]:
print(review["published_at"], review["rating"], (review["text"] or "")[:80])Response
search and reviews return rows in items; profile returns one data object. reviews also returns a business header: the business the reviews belong to, with its overall rating and review count.
Every documented key is on every row; a value the listing does not have is null. Address parts follow Google's own levels. In Turkey, city is the district (Kadıköy), region the province (İstanbul) and neighborhood the mahalle.
A search that matches nothing is a valid answer: items is empty and the request fee is billed. A business that does not exist is not: profile and reviews answer 404 BUSINESS_NOT_FOUND, and you are not charged.
The examples below are real responses, shortened. Reviewer names are replaced.
{
"success": true,
"type": "search",
"country": "TR",
"total": 450,
"count": 20,
"page": 1,
"limit": 20,
"sort": "reviews",
"direction": "desc",
"items": [
{
"name": "Asuman Moda",
"category": "Chocolate shop",
"categories": ["chocolate_shop", "cafe"],
"place_id": "ChIJX5XeCGG4yhQRH7cWWfY90-Q",
"cid": "16488590788999427871",
"address": "Caferağa, Şair Nefi Sk. No:9/a, 34710 Kadıköy/İstanbul",
"neighborhood": "Caferağa",
"city": "Kadıköy",
"region": "İstanbul",
"postal_code": "34710",
"country": "TR",
"phone": "+902163450526",
"website": "http://www.asuman.com/",
"domain": "www.asuman.com",
"latitude": 40.9837234,
"longitude": 29.026517,
"rating": 4.1,
"reviews": 8907,
"rating_distribution": { "1": 879, "2": 402, "3": 753, "4": 1511, "5": 5362 },
"price_level": null,
"claimed": true,
"photos": 4219,
"maps_url": "https://www.google.com/maps?cid=16488590788999427871&hl=en&gl=TR",
"updated_at": "2026-09-21T08:36:30Z"
}
],
"cost": 0.02112,
"cached": false,
"request_id": "0b7e6f1a-3c2d-4e5f-9a8b-1c2d3e4f5a6b"
}{
"success": true,
"type": "profile",
"country": "TR",
"language": "tr",
"data": {
"name": "Galata Kulesi",
"category": "Tarihi Yer",
"categories": ["Tarihi Yer", "Turistik Yer"],
"place_id": "ChIJQ3x3p-e5yhQRCzOLzD3Pdkw",
"cid": "5509819058466009867",
"address": "Bereketzade, 34421 Beyoğlu/İstanbul",
"neighborhood": "Bereketzade",
"city": "Beyoğlu",
"region": "İstanbul",
"postal_code": "34421",
"country": "TR",
"phone": "+902122490344",
"website": "https://galatakulesi.gov.tr/",
"domain": "galatakulesi.gov.tr",
"latitude": 41.025569,
"longitude": 28.9741286,
"rating": 4.6,
"reviews": 219004,
"rating_distribution": { "1": 3922, "2": 2572, "3": 11185, "4": 31764, "5": 169561 },
"price_level": null,
"claimed": true,
"photos": 399916,
"maps_url": "https://www.google.com/maps?cid=5509819058466009867",
"updated_at": null,
"description": "En üst katındaki restoranıyla boğaza tepeden bakan, restore edilmiş 14. yüzyıl kulesi ve eski zindan.",
"open_now": "closed",
"hours": {
"monday": [{ "open": "08:30", "close": "22:00" }],
"tuesday": [{ "open": "08:30", "close": "22:00" }]
},
"attributes": {
"available": [],
"unavailable": ["has_wheelchair_accessible_entrance"]
},
"topics": [],
"people_also_search": []
},
"cost": 0.00594,
"cached": false,
"request_id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a"
}{
"success": true,
"type": "reviews",
"country": "TR",
"language": "tr",
"count": 50,
"limit": 50,
"sort": "newest",
"business": {
"name": "Galata Kulesi",
"place_id": "ChIJQ3x3p-e5yhQRCzOLzD3Pdkw",
"cid": "5509819058466009867",
"rating": 4.6,
"reviews": 219004
},
"items": [
{
"review_id": "Ci9DQUlRQUNvZENodHljRjlvT2xkSFJXRkJRVTAyVDNweVRIQXhkM0oxUm1oVGNYYxAB",
"rating": 5,
"text": "Müzekart ile ücretsiz giriş mevcut. İçeride asansör de var, en üst kata çıkabiliyorsunuz.",
"original_text": null,
"original_language": "tr",
"published_at": "2026-10-20T17:29:15Z",
"url": "https://www.google.com/maps/reviews/data=!4m8!14m7…",
"images": 7,
"author": {
"name": "Ayşe Y.",
"url": "https://www.google.com/maps/contrib/…/reviews",
"reviews": 88,
"photos": 113,
"local_guide": true
},
"owner_answer": null
}
],
"cost": 0.004125,
"cached": false,
"request_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d"
}Response Fields Reference
Envelope
| Field | Type | Description |
|---|---|---|
country | string | The country searched. Absent on a search bounded only by a location |
language | string | profile and reviews: the language used |
total | integer | search: how many businesses match. Only the first 20,000 are reachable by paging |
count | integer | Rows in items; on search, the businesses you are billed for |
limit, page, sort, direction | The paging and order applied. On reviews, limit is the rounded number billed | |
business | object | reviews only: name, place_id, cid, rating and reviews (the total review count) |
Business (search row, profile → data)
| Field | Type | Description |
|---|---|---|
name | string | Business name |
category, categories | string | null, string[] | Primary category. On search, categories holds category ids you can pass back to categories. On profile it holds category names in language |
place_id, cid | string | null | Identifiers for profile and reviews |
address, neighborhood, city, region, postal_code, country | string | null | Full address and its parts |
phone, website, domain | string | null | Contact details as listed |
latitude, longitude | number | null | Location |
rating, reviews | number | null, integer | null | Average rating (1–5) and number of reviews |
rating_distribution | object | null | Review count per star, "1"–"5" |
price_level | string | null | e.g. inexpensive, moderate, expensive |
claimed | boolean | null | Whether the owner has claimed the listing |
photos | integer | null | Number of photos |
maps_url | string | null | Google Maps link to the business |
updated_at | string | null | search: when the listing was last refreshed, ISO 8601 UTC. null on profile, which is read live |
profile only
| Field | Type | Description |
|---|---|---|
description | string | null | The business's own description |
open_now | string | null | Status at the time of the request: open, closed, or another status such as temporarily_closed |
hours | object | null | Weekday (monday…sunday) → list of {open, close}, HH:MM local time. close 00:00 is midnight |
attributes | object | available and unavailable amenity ids, such as has_wheelchair_accessible_entrance |
topics | {topic, mentions}[] | Subjects reviewers mention most |
people_also_search | {name, place_id, cid, rating, reviews}[] | Businesses people also look at |
reviews row
| Field | Type | Description |
|---|---|---|
review_id | string | null | Stable review id |
rating | number | null | Stars, 1–5 |
text | string | null | Review text. When the review was translated into language, the original is in original_text. Otherwise original_text is null |
original_language | string | null | The language the reviewer wrote in |
published_at | string | null | ISO 8601 UTC |
url | string | null | Link to the review |
images | integer | Photos attached |
author | object | name, url, reviews and photos (the author's totals), and local_guide |
owner_answer | object | null | The owner's reply: text, published_at |
Errors
Every error carries a stable code, a readable error, and retryable; where it helps also field, hint and allowed. Send Accept-Language: tr for Turkish messages. You are never charged for an error. The full list, shared with the other APIs, is on Error Codes. Specific to this endpoint:
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 202 | — | Not an error: still running after wait seconds (usual for reviews) | Poll GET /v1/tasks/{task_id}; see Tasks |
| 400 | SEARCH_CRITERIA_REQUIRED | A search with no categories, name or location | Send at least one |
| 400 | UNKNOWN_CATEGORY | A category id that does not exist; hint suggests the closest | GET /v1/business-data/categories?q=… |
| 400 | BUSINESS_REQUIRED | profile or reviews without place_id, cid or name | Send one |
| 400 | PAGE_OUT_OF_RANGE | page × limit over 20,000 | Narrow the search |
| 400 | INVALID_PARAMETER | e.g. a location missing one of its three fields, a radius that is not a whole number from 1 to 500, two identifiers at once, or a cid sent as a number; field names it | Fix the field |
| 400 | UNSUPPORTED_MARKET | Unknown country or language | GET /v1/business-data/markets |
| 404 | BUSINESS_NOT_FOUND | No business matches the place_id, cid or name (not charged) | Check it, add the city to the name, or use an id from search |
{
"error": "'cafee' is not a business category",
"code": "UNKNOWN_CATEGORY",
"field": "categories",
"hint": "Did you mean 'cafe'? GET /v1/business-data/categories?q=… searches every category id",
"retryable": false
}{
"error": "No business was found",
"code": "BUSINESS_NOT_FOUND",
"hint": "Check the name, add the city to it, or use place_id or cid from type=search. You were not charged",
"retryable": false,
"request_id": "1e2d3c4b-5a6f-4e7d-8c9b-0a1f2e3d4c5b"
}Credits & Limits
| Request | Cost |
|---|---|
search | $0.0132 + $0.000396 per business returned ($0.0528 for 100) |
profile | $0.00594 |
reviews | $0.000825 per 10 reviews asked for ($0.00825 for 100) |
search is billed on the businesses it returns. A search for 100 that finds 3 costs $0.0132 + 3 × $0.000396. One that finds none still costs the $0.0132 request fee.
reviews is billed on the limit you ask for, rounded up to a multiple of 10, even when the business has fewer reviews. The review count isn't known until the reviews are collected. Size limit with the reviews count that search and profile return: asking for 100 reviews of a business that has 4 costs the 100.
Results are cached: 24 hours for search and profile, 6 hours for reviews. A cached answer is marked "cached": true and billed at the same price as a live one.
Per-minute and per-day limits are set on your account (429 RATE_LIMIT_EXCEEDED). At most 5 requests per API user run at the same time; more get 429 TOO_MANY_CONCURRENT_REQUESTS with Retry-After. A request that has returned 202 keeps its place until its task finishes. At most 100 tasks per API user can be unfinished at once (429 TOO_MANY_PENDING_TASKS). Polling has its own limit and does not count against any of these; see Tasks.
