Every business returned by the Online scraper, the REST API, and the MCP server uses one versioned record schema. This guide explains what each field means, how contact details are found, and how to read an empty value correctly before the data reaches your spreadsheet or CRM. The Search API wraps these records in businesses, alongside total and params. MCP returns that response in structuredContent; it does not require a separate results request.
Structure
Anatomy of a record
Example Garden Hotel
Hotel · Café · Meeting venue
4.7(128 reviews) · $$
- 100 Example Lane, Austin, TX 78701, US
- +1 512-555-0100, +1 512-555-0101
- https://hotel.example
- [email protected]
- Monday: Open 24 hours · America/Chicago
- place.placeId
- sample-place-id
- place.cid
- 1234567890123456789
- place.location
- 30.2672, -97.7431
- place.address.city
- Austin
- place.domain
- hotel.example
- place.claimed
- true
- place.enrichmentState
- returned
- place.observedAt
- 2026-09-22T00:00:00Z
In JSON, each record has a schemaVersion (currently 2026-09-22) and a place object with every field. CSV and Excel exports flatten the same data into named columns. Fields are always present in JSON — when a value is unknown it is null or an empty array, never omitted.
{
"schemaVersion": "2026-09-22",
"place": {
"placeId": "sample-place-id",
"cid": "1234567890123456789",
"name": "Example Garden Hotel",
"category": "Hotel",
"address": {
"formatted": "100 Example Lane, Austin, TX 78701, US",
"street": "100 Example Lane",
"city": "Austin",
"region": "Texas",
"postalCode": "78701",
"countryCode": "us",
"municipality": "Downtown"
},
"location": {
"latitude": 30.2672,
"longitude": -97.7431
},
"phone": "+1 512-555-0100",
"website": "https://hotel.example",
"rating": 4.7,
"reviewCount": 128,
"emails": [
"[email protected]"
],
"…": "…"
}
}Reference
Field groups
Identity
| Field | Meaning |
|---|---|
name | Business name as shown on Google Maps. |
category · categories | Primary category and the full list of categories. |
placeId · cid · dataId · kgmid | Google identifiers. Use them to match and de-duplicate records (dataId appears as Fid in CSV). |
businessStatus | For example OPERATIONAL or CLOSED_TEMPORARILY, when shown. |
claimed · owner | Whether the listing is owner-verified, and the owner profile when public. |
Location
| Field | Meaning |
|---|---|
address.formatted | Full one-line address. |
address.street · municipality · city · region · postalCode · countryCode | Parsed components. Some may be null when Google Maps does not show them separately. |
location.latitude · longitude | Pin coordinates, for mapping and distance calculations. |
plusCode · timeZone | Open Location Code and IANA time zone, when available. |
Contact
| Field | Meaning |
|---|---|
phone · phones | Primary number and every listed number. |
website · domain | Website on the listing and its hostname. |
emails · socialProfiles | Discovered on the business website during enrichment (see below). |
contacts[] | Evidence for each discovered email or social link: value, source page URL, method, and time observed. |
Reputation & media
| Field | Meaning |
|---|---|
rating · reviewCount | Average star rating (0–5) and total number of reviews on the listing. |
reviews · reviewsState | Up to 10 sample reviews when returned, and whether they were returned, unavailable, or not requested. |
photos · photosState · featuredImageUrl | Up to 20 photo/video metadata records and the listing’s main image. |
reviewUrl · mapsUrl · knowledgeUrl | Links back to the source listing. |
Details & provenance
| Field | Meaning |
|---|---|
openingHours | Object keyed by weekday, each an array of time ranges such as "9 AM–5 PM". |
description · priceRange · amenities · about · hotelClass · note | Listing attributes; about groups options such as Accessibility or Amenities. |
observedAt · language | When the listing was captured and in which language. |
enrichmentState · contactsCheckedAt · fieldStates | How complete the contact enrichment is, and per-field status (see Interpret missing values). |
Contact data
Where emails and social profiles come from
Google Maps listings show a phone number and website, but not emails. When enrichment runs, G Maps Crawl visits the listed website — the homepage and up to a few likely contact pages — and extracts business email addresses and social profile links. Each finding is recorded in contacts[] with the exact page it came from.
[
{
"kind": "email",
"value": "[email protected]",
"sourceUrl": "https://hotel.example/contact",
"observedAt": "2026-09-22T00:01:00Z",
"method": "website"
},
{
"kind": "social",
"value": "https://www.instagram.com/examplegardenhotel/",
"sourceUrl": "https://hotel.example",
"observedAt": "2026-09-22T00:01:00Z",
"method": "website"
}
]| Product | How to get emails & social profiles |
|---|---|
| Online scraper | Included on paid Online plans (Lite and above). Not available on Free. |
| REST API / MCP | Request extra: true on REST POST /api/v1/search or MCP search_google_maps. The default is false; contact availability depends on the source. |
Data quality
Interpret missing values
An empty value means “not found in the public sources we checked”, not “this business has no phone or email”. Three state fields tell you why something is empty:
| enrichmentState | Meaning |
|---|---|
| returned | The website was checked and at least one email or social link was found. |
| not_found | The website was checked; no contact details were published on the pages visited. |
| not_applicable | The listing has no website, so there was nothing to check. |
| failed | The website could not be read (timeout, blocked, or error). Retrying later may help. |
| pending | Enrichment was not run or has not finished for this record. |
fieldStates gives the same kind of answer for individual fields — observed, not_found, unsupported, failed, or pending — for example { "emails": "not_found", "socialProfiles": "observed" }. reviewsState and photosState do the same for sample reviews and photos.
Measure coverage before you commit
// Pass the businesses array from a search response, or Online JSON records
const fields = ["phone", "website", "emails", "socialProfiles", "rating", "openingHours"];
function coverage(records) {
return Object.fromEntries(
fields.map((field) => {
const present = records.filter(({ place }) => {
const value = place[field];
return Array.isArray(value) ? value.length > 0 : value !== null && value !== undefined;
}).length;
return [field, `${Math.round((present / records.length) * 100)}%`];
}),
);
}
// → { phone: "92%", website: "78%", emails: "41%", socialProfiles: "55%", rating: "97%", openingHours: "88%" }Joining data
Match and de-duplicate records
Chains, franchises, and businesses with common names often share a name across many locations. Overlapping searches (for example neighboring neighborhoods) also return the same listing more than once. Match on identifiers in this order:
placeId— CSV column Place Idcid— CSV column CiddataId— CSV column Fid- Only as a last resort: name plus formatted address
// Prefer stable identifiers, never the business name alone
function identity({ place }) {
return place.placeId ?? place.cid ?? place.dataId ?? `${place.name}|${place.address.formatted}`;
}
function dedupe(records) {
const seen = new Map();
for (const record of records) {
const key = identity(record);
const previous = seen.get(key);
// Keep the most recently observed copy
if (!previous || previous.place.observedAt < record.place.observedAt) seen.set(key, record);
}
return [...seen.values()];
}CSV & Excel
Spreadsheet columns
CSV and Excel files use the same 36 columns, in this order. Lists such as Phones, Email, and Categories are joined with ; , booleans become Yes/No, and nested objects such as Opening Hours become Monday: 9 AM–5 PM; Tuesday: ….
| # | Column | JSON path (under place) |
|---|---|---|
| 1 | Name | name |
| 2 | Description | description |
| 3 | Fulladdress | address.formatted |
| 4 | Street | address.street |
| 5 | Municipality | address.municipality |
| 6 | Categories | categories |
| 7 | About | about |
| 8 | Plus Code | plusCode |
| 9 | Time Zone | timeZone |
| 10 | Price | priceRange |
| 11 | Note | note |
| 12 | Amenities | amenities |
| 13 | Hotel Class | hotelClass |
| 14 | Phone | phone |
| 15 | Phones | phones |
| 16 | Claimed | claimed |
| 17 | Owner | owner.name |
| 18 | Owner Id | owner.id |
| 19 | Owner Link | owner.profileUrl |
| 20 | emails | |
| 21 | Social Medias | socialProfiles |
| 22 | Review Count | reviewCount |
| 23 | Average Rating | rating |
| 24 | Review URL | reviewUrl |
| 25 | Google Maps URL | mapsUrl |
| 26 | Google Knowledge URL | knowledgeUrl |
| 27 | Latitude | location.latitude |
| 28 | Longitude | location.longitude |
| 29 | Website | website |
| 30 | Domain | domain |
| 31 | Opening Hours | openingHours |
| 32 | Featured Image | featuredImageUrl |
| 33 | Cid | cid |
| 34 | Fid | dataId |
| 35 | Place Id | placeId |
| 36 | Kgmid | kgmid |
Recap
Before you import
- Decide which fields your workflow actually needs, and check their coverage first.
- Treat empty contact fields as unknown; read enrichmentState before drawing conclusions.
- De-duplicate on Place Id / Cid, not on name.
- Keep observedAt and source URLs so you can tell how fresh each record is.
- Remember that rating and review count describe the listing; they are not individual review records.
- Review the public data notice and the rules that apply to how you use contact data.