Introduction
The Livescraper API exposes every scraper in our toolkit as a small REST endpoint family. Send a JSON-encoded list of queries plus a few flags and we return clean, deduplicated rows of public data — same auth, same shape, same friendly pricing across Google Maps, Reviews and Email enrichment.
- Base URL:
https://apiv1.livescraper.com - Auth:
X-API-KEYheader on every request (a?key=query-string fallback is also accepted) - Format: single
GETrequest in / JSON out — no async/queue layer to manage - Per request: up to 15 queries in the
queriesarray - Cost: a baseline of 200 credits / query reserved on submit; you only pay for rows actually returned
- Throughput: ~15 requests / second per account (scalable on request)
GET /api/v1/task/{map|review|email} with your queries=[…], the connection holds open while we run the scrape, and the JSON results come back in the same response. There is no separate "create task" step, no polling, no webhooks — set a generous client timeout and read the body.Official SDKs
Official SDKs are available for Python, Node.js and PHP — each is a thin wrapper around the REST endpoints. If your language isn't listed, the HTTP API works fine with any HTTP client. Explore the Livescraper GitHub page for additional examples.
livescraper
Idiomatic Python client. from livescraper import ApiClient and you're three lines from a result.
livescraper
Promise-based wrapper for Node 14+. require('livescraper'), instantiate with your key, call .googleMapsSearch(...).
livescraper-php
Composer package for PHP 7.4+. PSR-18 HTTP client compatible.
composer require livescraper/sdkHello-world in every language
curl -X GET "https://apiv1.livescraper.com/api/v1/task/map?queries=%5B%22Restaurants%20Brooklyn%22%5D&language=en®ion=US" \ -H "X-API-KEY: YOUR-API-KEY" \ --max-time 600
# pip install livescraper from livescraper import ApiClient api = ApiClient("YOUR-API-KEY") rows = api.google_maps_search( queries=["Restaurants Brooklyn"], language="en", region="US", ) print(rows["data"][:3])
// npm install livescraper const Livescraper = require("livescraper"); const api = new Livescraper("YOUR-API-KEY"); const res = await api.googleMapsSearch( '["Restaurants Brooklyn"]', { language: "en", region: "US" }, ); console.log(res.data.slice(0, 3));
// composer require livescraper/sdk use Livescraper\ApiClient; $api = new ApiClient("YOUR-API-KEY"); $res = $api->googleMapsSearch( ["Restaurants Brooklyn"], ["language" => "en", "region" => "US"], ); print_r(array_slice($res["data"], 0, 3));
// no official SDK — use net/http req, _ := http.NewRequest("GET", `https://apiv1.livescraper.com/api/v1/task/map?queries=["Restaurants Brooklyn"]&language=en®ion=US`, nil) req.Header.Set("X-API-KEY", "YOUR-API-KEY") resp, _ := (&http.Client{Timeout: 10 * time.Minute}).Do(req) defer resp.Body.Close()
Authentication
Every request requires an X-API-KEY header. Your API key lives in the dashboard under Settings → API. Keep it secret; if it leaks, rotate it from the same screen.
X-API-KEY: YOUR-API-KEY
Query-string fallback. For tooling that can't easily set headers (browser bookmarks, some BI connectors) the key may also be passed as ?key=YOUR-API-KEY. The header takes precedence if both are present.
https://apiv1.livescraper.com/api/v1/task/map?key=YOUR-API-KEY&queries=%5B%22Restaurants+Brooklyn%22%5D
Access-Control-Allow-Origin: *). Browser fetch() calls from your site will fail. Always call the API from a server you control and proxy results to the browser — this also keeps your key out of the page source.Quick start
Four steps to your first API call:
- 1 · Sign in to the dashboard and copy your key from Settings → API
- 2 · Pick an endpoint —
/task/map,/task/review, or/task/email - 3 · Build a
queriesJSON array (up to 15 items). URL-encode it - 4 · Send a
GETwith theX-API-KEYheader. Set your client timeout to 600s — the response holds open until the scrape finishes
curl -X GET "https://apiv1.livescraper.com/api/v1/task/map?queries=%5B%22Restaurants+in+Brooklyn%22%5D&language=en®ion=US" \ -H "X-API-KEY: YOUR-API-KEY"
from livescraper import ApiClient scraper = ApiClient("YOUR-API-KEY") results = scraper.google_maps_search( queries=["Restaurants in Brooklyn"], language="en", region="US", ) print(results)
const livescraper = require("livescraper"); const client = new livescraper("YOUR-API-KEY"); client.googleMapsSearch('["Restaurants in Brooklyn"]', { language: "en", region: "US", }).then(response => console.log(response));
How results are delivered
The Livescraper API is synchronous. There is no separate "create task" then "poll for results" pattern, and no webhook callback. Submit the request, hold the connection open, and the rows come back in the same response body.
- Connection lifetime · Most Maps and Email runs return in 5–60 seconds. Long-tail Reviews pulls with many places can run for several minutes.
- Recommended client timeout ·
600s(10 min). Anything lower risks cutting off legitimate completions; anything higher is rarely needed. - Connection-pool sizing · Each in-flight request occupies one connection until it returns. Size your client pool to expected concurrent users.
- Idempotency · If a request times out client-side but the server completed, you may be billed for the work. Resending is safe — duplicate detection happens via
dropduplicates=True. - No webhooks · Need fire-and-forget plus a callback? Build a thin wrapper in your stack: enqueue the call, write results to your own queue/store on completion. We don't run a callback service.
Selecting fields with fields=
Every endpoint accepts an optional fields query parameter — a URL-encoded JSON array of column names. Pass it and the response is trimmed to just those columns; omit it and you get the full schema.
Use it to keep payloads small on bandwidth-sensitive networks, or to lock your downstream parser to a stable subset.
# Full schema (no fields=) GET /api/v1/task/map?queries=%5B%22Restaurants+Brooklyn%22%5D # Trimmed (only 4 columns) GET /api/v1/task/map?queries=%5B%22Restaurants+Brooklyn%22%5D&fields=%5B%22business_name%22%2C%22full_address%22%2C%22business_phone%22%2C%22average_rating%22%5D
- Order · Fields in the response follow the order of the API's native schema, not the order you pass.
- Unknown column names · Silently dropped — no error thrown. Validate column names against the per-endpoint tables below.
- Billing · Cost is the same whether or not you filter — the underlying scrape runs identically.
- Endpoint coverage · Schemas differ: Maps returns up to 56 columns, Reviews up to 27, Email up to 19. Full lists are in each endpoint section below.
Endpoints
Three sync GET endpoints, all under https://apiv1.livescraper.com/api/v1. They share the same auth, the same parameter shape, and the same { code, message, data } response envelope.
| Method | Path | What it does | Credits |
|---|---|---|---|
| GET | /api/v1/task/map | Search Google Maps for places. Returns business records. | 200 / query |
| GET | /api/v1/task/review | Pull recent or all reviews for any business or place. | 200 / query |
| GET | /api/v1/task/email | Extract emails, phones and socials from a list of websites. | 200 / query |
How credits work. Each query in your request reserves 200 credits on submit. Unused reserves are released back to your balance after the scrape completes; rows actually returned consume credits proportional to the per-endpoint per-row rates published on the Pricing page. If your balance can't cover the up-front reserve, the call returns 402 E00100014.
Google Maps Data
Get places from Google Maps based on your search query (or multiple queries). Results match what you'd see on the Google Maps site — same listings, same order. For best results, include a location in your query (e.g. Restaurants, CA, USA) since server IPs may be in different regions.
| Name | In | Type | Description |
|---|---|---|---|
| queriesrequired | query | string[] | URL-encoded JSON array. Anything you'd type in Google Maps — and also google_id (feature_id), place_id, or CID. Up to 15 items per request. Examples: "Real estate agency, Rome, Italy"; "restaurants, Brooklyn 11203"; "0x80857ea1255c8b71:0x4163509ac1eb5cf9"; "ChIJcYtcJaF-hYAR-VzrwZpQY0E". |
| dropduplicatesoptional | query | "True" | "False" | De-duplicate rows by place ID across queries. Default "True". |
| languageoptional | query | string | 2-letter language code (en, es, fr, …). Default "en". |
| regionoptional | query | string | 2-letter region code (US, GB, IN, …). Default "US". |
| enrichmentoptional | query | "True" | "False" | Run a second pass on each result's website to fill email_1..3, phone_1..3 and social fields. Default "False". Adds ~30% to runtime. |
| fieldsoptional | query | string[] | URL-encoded JSON array — whitelist of column names. Defaults to the full 56-column schema listed below. |
curl -X GET "https://apiv1.livescraper.com/api/v1/task/map?\ queries=%5B%22Restaurants+in+Alakanuk%2C+AK%2C+United+States%22%5D&\ dropduplicates=True&language=en®ion=US&enrichment=False&\ fields=%5B%22business_website%22%2C%22business_phone%22%2C%22sub_types%22%5D" \ -H "X-API-KEY: YOUR-API-KEY"
from livescraper import ApiClient scraper = ApiClient("YOUR-API-KEY") results = scraper.google_maps_search( queries=["Restaurants in Alakanuk, AK, United States"], language="en", region="US", dropduplicates="True", enrichment="False", fields=["business_website", "business_phone", "sub_types"], )
const livescraper = require("livescraper"); const client = new livescraper("YOUR-API-KEY"); client.googleMapsSearch( '["Restaurants in Alakanuk, AK, United States"]', { dropduplicates: "True", language: "en", region: "US", enrichment: "False", fields: '["business_website"]', } ).then(response => console.log(response));
Response shape (full, single row)
The envelope is { code, message, data }. data is a flat array — one row per business across all queries (with dropduplicates=True). One real row, abbreviated for readability:
{
"code": 200,
"message": "success",
"data": [
{
"query": "Restaurants in Brooklyn, NY",
"google_place_url": "https://www.google.com/maps/place/?q=place_id:ChIJ...",
"business_name": "Lilia",
"business_website": "https://www.lilianewyork.com/",
"business_phone": "+1 718-576-3095",
"type": "Italian restaurant",
"sub_types": "Italian restaurant, Bar, Restaurant",
"category": "Restaurant",
"full_address": "567 Union Ave, Brooklyn, NY 11211, USA",
"borough": "Brooklyn",
"street": "567 Union Ave",
"city": "Brooklyn",
"postal_code": "11211",
"state": "NY",
"country": "United States",
"country_code": "US",
"timezone": "America/New_York",
"latitude": 40.7166,
"longitude": -73.9501,
"plus_code": "PRMW+6F Brooklyn, NY",
"area_service": false,
"review_url": "https://search.google.com/local/reviews?placeid=ChIJ...",
"reviews_id": "4747295018931215485",
"total_reviews": 1842,
"average_rating": 4.6,
"reviews_per_score": "5:1331, 4:331, 3:110, 2:46, 1:24",
"reviews_per_score_1": 24,
"reviews_per_score_2": 46,
"reviews_per_score_3": 110,
"reviews_per_score_4": 331,
"reviews_per_score_5": 1331,
"working_hours": "{\"Monday\":\"Closed\",\"Tuesday\":\"5:30-10PM\",...}",
"working_hours_old_format": "Monday: Closed | Tuesday: 5:30–10 PM | ...",
"popular_time": "{\"Tuesday\":{\"20\":78,\"21\":92,...},...}",
"about": "{\"Service options\":[\"Dine-in\",\"Takeout\"],...}",
"posts": null,
"description": "Refined Italian eatery offering housemade pasta & wood-fired fare in a stylish setting.",
"logo_url": "https://lh3.googleusercontent.com/p/AF1Q...",
"photos_count": 1247,
"photo_url": "https://lh3.googleusercontent.com/p/AF1Q...",
"street_view": "https://lh3.googleusercontent.com/p/AF1Q...",
"price_range": "$$$",
"business_status": "OPERATIONAL",
"is_verified": true,
"owner_title": "Lilia",
"owner_link": "https://www.google.com/maps/contrib/108...",
"owner_id": "108...",
"reserving_table_links": "https://resy.com/cities/ny/lilia",
"booking_appointment_link": null,
"order_link": null,
"menu_link": "https://www.lilianewyork.com/menu",
"place_id": "ChIJN1t_tDeuwokRTbApSDH3J3w",
"google_id": "0x89c25855e8aabbcc:0x...",
"place_cid": "4747295018931215485",
"located_in": null,
"located_google_id": null
}
// …more rows, one per business, up to ~500/query
]
}
Available fields (56 columns)
Listed in the order returned when fields= is omitted. Star fields are filled by the optional enrichment=True pass.
// Query & identity query, google_place_url, business_name, place_id, google_id, place_cid // Contact business_website, business_phone // Category type, sub_types, category // Address full_address, borough, street, city, postal_code, state, country, country_code, timezone, latitude, longitude, plus_code, area_service // Reviews summary (counts only — full review text lives at /task/review) review_url, reviews_id, total_reviews, average_rating, reviews_per_score, reviews_per_score_1, reviews_per_score_2, reviews_per_score_3, reviews_per_score_4, reviews_per_score_5 // Hours & activity working_hours, working_hours_old_format, popular_time // Content & media about, posts, description, logo_url, photos_count, photo_url, street_view // Status & signals price_range, business_status, is_verified // Owner / GMB engagement owner_title, owner_link, owner_id // Conversion / action links reserving_table_links, booking_appointment_link, order_link, menu_link // Sub-location relations located_in, located_google_id // Filled only when enrichment=True (from the result's website) email_1, email_2, email_3, phone_1, phone_2, phone_3, facebook, instagram, linkedin, twitter, youtube
Google Maps Reviews
Pull reviews for a place (or multiple places) — author, rating, full review text, owner reply, photos and timestamps. Same query syntax as /task/map; accepts a query string, place_id, CID or google_id.
| Name | In | Type | Description |
|---|---|---|---|
| queriesrequired | query | string[] | URL-encoded JSON array. Place query, place_id, CID or google_id. Up to 15 items. |
| dropduplicatesoptional | query | "True" | "False" | De-duplicate by review_id. Default "True". |
| languageoptional | query | string | 2-letter language code (filters reviews to that language). Default "en". |
| regionoptional | query | string | 2-letter region code. Default "US". |
| fieldsoptional | query | string[] | URL-encoded JSON array — whitelist of columns. Defaults to the full 27-column schema. |
sorting_method=5 ("most recent") and a hard cap of ~500 reviews per place per call. For brands with longer review tails, the no-code dashboard supports an "all time" pull with auto-pagination.curl -X GET "https://apiv1.livescraper.com/api/v1/task/review?\ queries=%5B%22ChIJN1t_tDeuwokRTbApSDH3J3w%22%5D&\ dropduplicates=True&language=en®ion=US" \ -H "X-API-KEY: YOUR-API-KEY" \ --max-time 600
reviews = api.google_review_search(
queries=["ChIJN1t_tDeuwokRTbApSDH3J3w"],
language="en", region="US",
)
print(reviews["data"][0])const res = await api.googleReviewSearch( '["ChIJN1t_tDeuwokRTbApSDH3J3w"]', { language: "en", region: "US" }, ); console.log(res.data[0]);
Response shape (full, single row)
{
"code": 200,
"message": "success",
"data": [
{
// Place context (denormalised on every review row)
"query": "ChIJN1t_tDeuwokRTbApSDH3J3w",
"business_name": "Lilia",
"google_id": "0x89c25855e8aabbcc:0x...",
"place_id": "ChIJN1t_tDeuwokRTbApSDH3J3w",
"place_cid": "4747295018931215485",
"google_place_url": "https://www.google.com/maps/place/?q=place_id:ChIJ...",
"review_url": "https://search.google.com/local/reviews?placeid=ChIJ...",
"reviews_id": "4747295018931215485",
"reviews_per_score": "5:1331, 4:331, 3:110, 2:46, 1:24",
"total_reviews": 1842,
"average_rating": 4.6,
// Review identity
"review_id": "ChdDSUhNMG9nS0VJQ0FnSURwMl...",
"review_link": "https://www.google.com/maps/reviews/data=!4m...!8m...",
// Author
"author_title": "Sarah K.",
"author_link": "https://www.google.com/maps/contrib/108...",
"author_id": "108...",
"author_image": "https://lh3.googleusercontent.com/a/AC...",
// Content
"review_text": "Exceptional pasta and an attentive front of house. Skip the line, book on Resy.",
"review_rating": 5,
"review_img_url": "https://lh3.googleusercontent.com/p/AF1Q...",
"review_img_urls": "https://lh3.../1.jpg | https://lh3.../2.jpg",
"review_likes": 3,
// Timing
"review_timestamp": 1746878520,
"review_datetime_utc": "2026-05-10T14:22:00.000Z",
// Owner reply
"owner_answer": "Thank you Sarah — see you next time!",
"owner_answer_timestamp": 1746965520,
"owner_answer_timestamp_datetime_utc": "2026-05-11T14:32:00.000Z"
}
// …more reviews, sorted most-recent first
]
}
Available fields (27 columns)
// Place context (same on every review row — handy for joins) query, business_name, google_id, place_id, place_cid, google_place_url, review_url, reviews_id, reviews_per_score, total_reviews, average_rating // Review identity review_id, review_link // Author author_title, author_link, author_id, author_image // Content review_text, review_rating, review_img_url, review_img_urls, review_likes // Timing review_timestamp, review_datetime_utc // Owner reply owner_answer, owner_answer_timestamp, owner_answer_timestamp_datetime_utc
Email & Contact
Hand over a list of domains or website URLs. The API fetches each, parses the homepage and common contact pages, and returns deliverable emails, phone numbers, social handles, tech-stack hints and basic SEO metadata.
| Name | In | Type | Description |
|---|---|---|---|
| queriesrequired | query | string[] | URL-encoded JSON array of domains or website URLs. Up to 15 items. Accepts https://example.com, example.com, or www.example.com — all normalised internally. |
| fieldsoptional | query | string[] | URL-encoded JSON array — whitelist of columns. Defaults to the full 19-column schema. |
email_1. Chains and corporate sites are lower (gated inboxes). All other fields (phone, socials, host, tech stack) return at much higher rates.curl -X GET "https://apiv1.livescraper.com/api/v1/task/email?\ queries=%5B%22https%3A%2F%2Fwww.lilianewyork.com%22%5D" \ -H "X-API-KEY: YOUR-API-KEY" \ --max-time 120
contacts = api.google_email_search(
queries=["https://www.lilianewyork.com"],
)
print(contacts["data"][0])const res = await api.googleEmailSearch( '["https://www.lilianewyork.com"]', ); console.log(res.data[0]);
Response shape (full, single row)
{
"code": 200,
"message": "success",
"data": [
{
"org_link": "https://www.lilianewyork.com/",
"host": "185.230.63.107",
"domain_status": 200,
"contact_page": "https://www.lilianewyork.com/contact",
// Emails — slot 1..3 plus an ordered, pipe-joined "all"
"email_1": "info@lilianewyork.com",
"email_2": "reservations@lilianewyork.com",
"email_3": "press@lilianewyork.com",
"all_emails": "info@lilianewyork.com | reservations@lilianewyork.com | press@lilianewyork.com",
// Phones
"phone_1": "+1 718-576-3095",
"phone_2": null,
"phone_3": null,
"all_phones": "+1 718-576-3095",
// Socials
"facebook": "https://www.facebook.com/lilianewyork",
"instagram": "https://www.instagram.com/lilianewyork",
"linkedin": null,
"twitter": null,
"youtube": null,
// Site metadata
"website_title": "Lilia | Williamsburg, Brooklyn",
"website_desc": "Refined Italian eatery offering housemade pasta & wood-fired fare.",
"website_built_with": "Squarespace 7.1"
}
]
}
Available fields (19 columns)
// Source & reachability org_link, host, domain_status, contact_page // Emails (slot 1..3 plus pipe-joined "all_emails") email_1, email_2, email_3, all_emails // Phones (slot 1..3 plus pipe-joined "all_phones") phone_1, phone_2, phone_3, all_phones // Socials facebook, twitter, instagram, youtube, linkedin // Site metadata website_title, website_desc, website_built_with
Error codes
The API uses standard HTTP status codes. Every error response includes a stable code string you can match on programmatically — the codes are versioned and won't change without notice.
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 200 | — | Success. Rows in data. | Iterate data[]. Empty array = scrape succeeded but matched zero results. |
| 401 | E00100012 | Wrong or missing API key. | Verify the X-API-KEY header. Regenerate in dashboard if needed. |
| 402 | E00100014 | Credit balance too low to cover the 200/query reserve. | Top up at /pricing, then retry. |
| 422 | E00100016 | queries missing, malformed JSON, or more than 15 items. | URL-encode a valid JSON array. Split into multiple requests for >15 items. |
| 429 | — | Per-account QPS exceeded (~15 req/s default). | Honour the Retry-After header if present, otherwise back off ~2s and retry. Contact support to raise the cap. |
| 5xx | — | Upstream scrape engine failed (rare; we monitor 24/7). | Retry once with the same payload. Open a ticket if it repeats. |
Error response shape
Every error returns the same envelope, regardless of endpoint. error.code is the value to switch on in your client.
{
"status": {
"statusCode": 401,
"statusMessage": "Request Failed"
},
"error": {
"title": "Auth Error",
"message": "Auth not found!",
"info": {
"type": "fullScreen",
"data": {
"description": "Wrong or missing API Key (token) or contact support",
"cta": "retry",
"label": "Retry"
}
},
"type": "error",
"code": "E00100012"
}
}
Per-endpoint quirks
| Endpoint | Edge case | Behaviour |
|---|---|---|
/task/map | No results match query | Returns 200 with empty data: []. Credits for that query are released. |
/task/map | Invalid place_id in queries | That single query yields no rows; other queries in the same batch still run. |
/task/review | Place exists but has 0 reviews | 200 with an empty data: [] for that query. |
/task/email | Domain returns 5xx / unreachable | 200 with domain_status set to the upstream code and contact fields null. |
/task/email | Domain bans crawlers (robots.txt) | We respect robots.txt; row returns with domain_status populated but contact fields empty. |
Rate limits
Default per-account throughput is around 15 requests / second across all endpoints combined. Each request can carry up to 15 queries, so 15 req/s × 15 queries = a practical ceiling of ~225 queries / second with no special arrangement.
| Limit | Default | How to raise |
|---|---|---|
| Requests / second | ~15 | Email support; raised on a per-account basis (usually within one business day). |
| Queries per request | 15 | Hard cap. Split large jobs into parallel requests, or use the no-code dashboard for jobs up to 500K rows. |
| Concurrent in-flight | unlimited (subject to QPS) | Each request blocks for the scrape duration. Size your client connection pool accordingly. |
| Connection timeout | ~10 min | We don't terminate slow scrapes server-side, but most clients should set their own max-time = 600s. |
Backoff & retry
If you receive a 429, check for a Retry-After response header (seconds) — wait that long, then retry. If the header is absent, an exponential backoff starting at 2s with jitter is the standard pattern. 5xx errors are safe to retry once with the same payload.
queries array is capped at 15 items. Need to scrape thousands? Send multiple parallel requests within your QPS budget, or use the no-code dashboard which auto-splits jobs up to 500K rows.API FAQ
| Question | Answer |
|---|---|
| Is the API really synchronous? | Yes. One GET, one response body. Connection holds open 5–60s (Maps / Email) or up to several minutes (long Reviews pulls). Set your client timeout to 600s. |
| Are there webhooks? | No callback service. If you need fire-and-forget, wrap the sync call in your own queue / worker — most teams find this gives them more control than a managed callback layer would. |
| How am I billed? | Each query reserves 200 credits on submit. After the scrape, the actual cost (based on rows returned) is deducted; any unused reserve is released back. 402 E00100014 if your balance can't cover the reserve. |
| Do I pay for empty results? | No. If a query returns zero rows, the reserve is released in full — you pay 0 for that query. |
| Can I call the API from a browser? | Not directly — we don't serve Access-Control-Allow-Origin. Proxy through your own backend so your key stays server-side. |
| Is there a sandbox / staging environment? | Not currently. Use a small queries array against the live API — at 200 credits / query, a single test costs ~$0.01. |
| Do you have an OpenAPI spec? | A draft openapi.json is in the pipeline. Until then, this page is the authoritative reference. |
| Is scraping Google legal? | Livescraper reads publicly available Maps data — the same data a logged-out user would see in the browser. For your jurisdiction's specifics, consult counsel. Most B2B use under legitimate-interest frameworks (GDPR, CCPA, CAN-SPAM) is fine if you include an unsubscribe path on outreach. |
Changelog
The API surface itself is stable; field schemas grow over time as Google exposes new data on Maps. Breaking changes (renamed / removed fields) are announced 30 days in advance.
| Date | Change |
|---|---|
| 2026-05-12 | Docs rewrite — accurate sync model, full real-field response shapes per endpoint, expanded error matrix, per-language SDK hello-worlds. Removed unused enrichment param on /task/review. |
| 2026-04-22 | /task/map: added plus_code, area_service, reserving_table_links fields. price_range now returns the dollar-sign string Google shows (e.g. $$$) instead of an integer band. |
| 2026-03-04 | PHP SDK 1.0 released. Python SDK bumped to 1.4. |
| 2026-02-10 | /task/review: owner_answer_timestamp_datetime_utc added alongside the existing Unix timestamp. |
| 2026-01-08 | /task/email: new contact_page field — URL of the discovered contact page when one exists. |
Need help?
If something's unclear, broken, or just feels weird, drop us a line. We're real engineers and we read every email — replies typically inside one business day, often within hours.