Guide · Python

Scrape Google Maps places and reviews to CSV with Python

One script, two jobs: every business Google Maps lists for a search, with phone, website and rating, and every review of a place, or only the ones since your last run.

ScrapingBot 10 min read
Endpoints
/maps, /reviews
Cost
10 credits per page of 20
Language
Python 3.9+
Needs
requests
On this page

Two questions send people to Google Maps with a spreadsheet open. Who are all the businesses of one kind in one place, and how do I reach them? And what are customers saying about this one, or about my competitors, this month? Maps answers both, twenty results at a time, in a panel you scroll.

This guide gets both as CSV files with Python: the places for a search with phone, website, rating and hours, then the reviews of any of them. Every response and output below is real, collected through ScrapingBot on November 10, 2026, for one search: coffee roasters in Portland, Oregon.

Before you start

  • A ScrapingBot API key. Create a free account for 100 credits: 10 calls, which is 200 places, or 100 places and 100 reviews. No card needed.
  • Python 3.9 or newer and pip install requests.
  • A search the way you would type it into Google Maps: what, and where.

Google's own API stops at 60 places and 5 reviews

The official route is the Places API, and for an app that shows places to its users it is the right one. Two documented limits decide whether it fits this job (all checked November 2026):

  • 60 places per search. Text Search returns up to 20 results a page and "a maximum of 60 results across all pages".
  • 5 reviews per place. Place Details returns "up to five user-submitted" reviews, ordered by relevance by default.

Phone, website, rating and hours bill at the Text Search Enterprise rate, $35.00 per 1,000 requests after 1,000 free a month; reviews at Place Details Enterprise + Atmosphere, $25.00 per 1,000 after 1,000 free (Google's price list). Its policies also limit what you may keep: place IDs indefinitely, coordinates for 30 days. Inside those limits, use it: it is Google's, the first thousand calls are free, and it has address components and photos that this guide does not touch. For the 61st place or the 6th review, read on.

Every Google call is a POST to https://scrapingbot.io/api/v1/google. The /maps endpoint takes the search in q:

curl -X POST https://scrapingbot.io/api/v1/google \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endpoint": "/maps", "params": {"q": "coffee roasters in portland oregon", "gl": "us"}}'

The answer holds 20 places. Here is the third, whole except for its image and booking links:

{
  "success": true,
  "searchParameters": { "q": "coffee roasters in portland oregon", "engine": "google", "gl": "us", "type": "maps" },
  "places": [
    …,
    {
      "position": 3,
      "title": "Roseline Coffee Cafe & Roastery",
      "address": "321 NE Davis St, Portland, OR 97232",
      "phoneNumber": "(503) 809-2890",
      "website": "http://roselinecoffee.com/",
      "rating": 4.7,
      "ratingCount": 210,
      "type": "Coffee shop",
      "types": ["Coffee shop", "Coffee roastery"],
      "openingHours": { "Monday": "7 AM–5 PM", "Tuesday": "7 AM–5 PM", "…": "…" },
      "latitude": 45.5245982,
      "longitude": -122.662453,
      "cid": "955739314569279103",
      "placeId": "ChIJ8WaP9JShlVQRf_bGxtd3Qw0",
      "fid": "0x5495a194f48f66f1:0xd4377d7c6c6f67f",
      "thumbnailUrl": "https://lh3.googleusercontent.com/…",
      "bookingLinks": [ "https://www.google.com/…" ]
    },
    …
  ],
  "ll": "@45.5371502,-122.643036,13z",
  "duration": "1.99",
  "statusCode": 200,
  "creditsUsed": 10
}
FieldWhat it is
cid, placeId, fidThree IDs for the same place. Key your records on cid; any of the three fetches the place's reviews. placeId is the one Google's own APIs use.
title, addressName and full address with city, state and postcode.
phoneNumber, websiteAs shown on the listing. Left out when the listing has none.
rating, ratingCountThe star average Google shows, and how many ratings it rests on.
type, typesThe main category, and every category on the listing.
openingHoursOne entry per weekday, in Google's wording ("Open 24 hours", "Closed").
latitude, longitudeWhere the pin is.
ll (top level)The map viewport Google searched, as @latitude,longitude,zoom. Page 2 needs it.

To look one place up again later, send its cid or placeId instead of q and you get that place alone. There is also a /places endpoint: 10 results a page with the name, street address, category, rating, website and coordinates, but no phone number, hours or placeId. Both cost 10 credits a call, so for a list of businesses /maps gives twice the rows and more columns for the same price.

Past the first 20, and where Google stops

Ask for "page": 2 on its own and the call is refused, free of charge:

{
  "success": false,
  "error": "Parameter \"ll\" (GPS location) is required for paginated maps search (page > 1)"
}

Page 1 answered with the viewport it searched ("ll": "@45.5371502,-122.643036,13z"). Send that back with every later page and the list continues. We kept going until Google ran out:

PagePlacesNewRoasteries
1202011
2201811
320198
420198
520194
620203
720202
815151
900

Eight pages, 155 results, 150 different places: two and a half times the official API's 60. ("Roasteries" counts the places with "Coffee roastery" among their types.) Four things in that table shape the script.

  • The end is a short page. Page 8 had 15 places. Page 9 came back with "places": [], a successful call that cost its 10 credits like any other. Stop at the first page with fewer than 20.
  • Pages overlap a little. Five places appeared twice. position restarts at 1 on every page, so it can't order the whole list; de-duplicate on cid.
  • Relevance fades. Google fills the list with whatever is nearest the query. On pages 1 and 2 more than half the places listed "Coffee roastery" as a category; by page 5 one in five did, and the rest were coffee shops. A made-up business name with "portland" after it returned 20 real Portland places rather than nothing. Filter on types or the name; the script takes a word to require.
  • The same search is not the same list twice. Five pages gave 94 different places in one run and 95 three minutes later, and the viewport page 1 answered with had moved a little in between.

Across the 150 places, 106 had a phone number, 138 a website, 138 opening hours and 149 a rating. A field the listing lacks is left out, not set to null, so read places with .get(). To cover more ground than one viewport, send your own ll with the first page: a search for "coffee roasters" with the viewport centred on Beaverton, eight miles to the west, returned 40 places in two pages, 11 of them not among the 150.

Reviews: every one, not five

/reviews takes a place's cid (or placeId, or fid) and returns 20 reviews a page:

curl -X POST https://scrapingbot.io/api/v1/google \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endpoint": "/reviews", "params": {"cid": "3336897143139170785", "sortBy": "newest"}}'

Old Friends Coffee Roasters, newest first, trimmed to one review:

{
  "success": true,
  "searchParameters": { "cid": "3336897143139170785", "engine": "google", "sortBy": "newest", "type": "reviews" },
  "reviews": [
    {
      "id": "Ci9DQUlRQUNvZENodHljRjlvT2pJM1R6aDFURlpGTVUxUFFrWnJXbDl5VDNWdlQwRRAB",
      "rating": 5,
      "date": "2 days ago",
      "isoDate": "2026-11-07T18:53:26.877Z",
      "snippet": "They have free wifi, a few food items, good drinks, 6 2top tables, good music, a few outlets and 2 bathrooms. …",
      "likes": 0,
      "link": "https://www.google.com/maps/reviews/data=…",
      "user": { "name": "…", "reviews": 48, "photos": 22, "link": "https://www.google.com/maps/contrib/…", … }
    },
    …
  ],
  "nextPageToken": "CjEIARIpCgoAP72GEjvdRZ__EhA6AyW6gYKQVSJQzyAAAAAAGgn95JUCYSO91FkYACIA",
  "duration": "0.67",
  "statusCode": 200,
  "creditsUsed": 10
}
  • Send nextPageToken back to get the next 20; the last page has none. The listing shows 101 reviews, and paging to the end returned 101 in 6 calls. For Sterling Coffee Roasters, which shows 839, it returned 839 in 42 calls with no duplicates, in about 30 seconds.
  • A rating without words has no snippet. 30 of the 101 were stars only, and 296 of the 839. Photos, when a review has them, are under media.
  • Use isoDate. date is Google's relative wording, and it follows the language: with "hl": "es" a two-day-old review read "Hace 2 días".
  • Four orders. sortBy takes mostRelevant (the default), newest, highestRating and lowestRating. With lowestRating, Sterling's first page was its 13 one-star reviews followed by 7 of its two-star ones: the complaints, in one call.
  • An unknown ID is not an error. A cid that matches no place returned "reviews": [] and was charged like any successful call.

With all of them in hand you can check Google's arithmetic: Old Friends' 101 ratings average 4.73 where the listing says 4.7, and Sterling's 839 average 4.75 where it says 4.8.

The script

Save this as maps.py. It has two commands: places runs a search and writes places.csv; reviews takes one cid, or a whole places.csv, and writes reviews.csv, back to a date if you give one.

import csv
import sys
import time
from collections import Counter
from concurrent.futures import ThreadPoolExecutor

import requests

API = "https://scrapingbot.io/api/v1/google"
HEADERS = {"x-api-key": "YOUR_API_KEY"}
CONCURRENCY = 1  # your plan's limit: Free 1, Starter 10, Startup 50
MAX_PAGES = 5    # pages of 20 places per search; later pages drift off the query
PLACE_FIELDS = ["title", "type", "address", "phoneNumber", "website", "rating", "ratingCount",
                "latitude", "longitude", "cid", "placeId", "hours"]
REVIEW_FIELDS = ["cid", "isoDate", "rating", "likes", "snippet", "id"]
DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
credits = []  # one entry per successful call: 10 credits each


def call(endpoint, params, tries=4):
    """One Google call: 10 credits on success, an empty page included. Failed calls cost nothing."""
    error = None
    for attempt in range(tries):
        try:
            res = requests.post(API, headers=HEADERS, timeout=60,
                                json={"endpoint": endpoint, "params": params})
            body = res.json()
        except (requests.RequestException, ValueError) as err:  # no answer, or not JSON
            error = err
        else:
            if body.get("success"):
                credits.append(10)
                return body
            if res.status_code in (400, 401, 402):  # retrying will not help
                raise RuntimeError(f"{res.status_code} {body.get('error')}")
            error = body.get("error")
        time.sleep(2 ** attempt)  # 408, 429, 5xx or a network error: wait and try again
    raise RuntimeError(f"kept failing: {error}")


def places(query, gl="us", max_pages=MAX_PAGES):
    """The places Google Maps lists for a query, 20 a page, de-duplicated on cid."""
    found, ll = {}, None
    try:
        for page in range(1, max_pages + 1):
            params = {"q": query, "gl": gl}
            if page > 1:
                params.update(ll=ll, page=page)  # later pages need the viewport page 1 answered with
            body = call("/maps", params)
            batch = body.get("places") or []
            for place in batch:
                found.setdefault(place["cid"], place)
            ll = ll or body.get("ll")
            if len(batch) < 20 or not ll:  # a short page is the last; an empty one would still cost 10
                break
    except RuntimeError as err:  # keep the pages already paid for
        print(f"stopped after {len(found)} places: {err}")
    return list(found.values())


def reviews(cid, since="", token=None):
    """A place's reviews, newest first, 20 a page, back to `since` (YYYY-MM-DD) or to the first.
    `token` is the cursor a stopped run printed: paging carries on from that page."""
    out = []
    try:
        while True:
            params = {"cid": cid, "sortBy": "newest"}
            if token:
                params["nextPageToken"] = token
            body = call("/reviews", params)
            for review in body.get("reviews") or []:
                if review["isoDate"][:10] < since:
                    return out
                out.append({**review, "cid": cid})
            token = body.get("nextPageToken")
            if not token:
                return out
    except RuntimeError as err:  # keep the pages already paid for, and say how to go on
        more = f'\n  to continue: python maps.py reviews {cid} "{since}" {token}' if token else ""
        print(f"{cid}: stopped after {len(out)} reviews: {err}{more}")
        return out


def cell(value):
    """Text that starts like a formula gets a leading ' so a spreadsheet shows it, not runs it."""
    return "'" + value if isinstance(value, str) and value.startswith(("=", "+", "-", "@", "\t", "\r")) else value


def save(path, fields, rows, append=False):
    with open(path, "a" if append else "w", newline="", encoding="utf-8") as f:
        out = csv.writer(f)
        if not append:
            out.writerow(fields)
        out.writerows([cell(row.get(field, "")) for field in fields] for row in rows)


if __name__ == "__main__":
    if len(sys.argv) < 3 or sys.argv[1] not in ("places", "reviews"):
        sys.exit('usage: maps.py places "<query>" [must-contain]  |  maps.py reviews <cid or places.csv> [since] [cursor]')
    if sys.argv[1] == "places":
        rows = places(sys.argv[2])
        word = sys.argv[3].lower() if len(sys.argv) > 3 else ""
        kept = [p for p in rows if word in (p["title"] + " " + " ".join(p.get("types") or [])).lower()]
        for p in kept:
            hours = p.get("openingHours") or {}
            p["hours"] = "; ".join(f"{day[:3]} {hours[day]}" for day in DAYS if day in hours
                                   ).replace("\u202f", " ")  # Google's narrow space before AM/PM
        save("places.csv", PLACE_FIELDS, kept)
        print(f"{len(rows)} places, {len(kept)} kept, {sum(credits)} credits")
        print(f"with a phone: {sum(1 for p in kept if p.get('phoneNumber'))}, "
              f"with a website: {sum(1 for p in kept if p.get('website'))}")
        for p in sorted(kept, key=lambda p: -(p.get("ratingCount") or 0))[:8]:
            print(f"{p['title'][:36]:<38}{p.get('rating', '-'):>4}{p.get('ratingCount', 0):>7,}"
                  f"  {p.get('phoneNumber', ''):<16}{p['cid']}")
    else:
        target, since = sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else ""
        token = None
        if target.endswith(".csv"):
            with open(target, newline="", encoding="utf-8-sig") as f:
                names = {r["cid"]: r["title"] for r in csv.DictReader(f)}
        else:
            names = {target: target}
            token = sys.argv[4] if len(sys.argv) > 4 else None  # one place, carried on where it stopped
        with ThreadPoolExecutor(max_workers=CONCURRENCY) as pool:
            lists = list(pool.map(lambda cid: reviews(cid, since, token), names))
        rows = [r for found in lists for r in found]
        save("reviews.csv", REVIEW_FIELDS, rows, append=bool(token))  # a continued run adds to the file
        print(f"{len(rows)} reviews for {len(names)} places, {sum(credits)} credits")
        for cid, found in zip(names, lists):
            if not found:
                continue
            stars = Counter(r["rating"] for r in found)
            mean = sum(r["rating"] for r in found) / len(found)
            text = sum(1 for r in found if r.get("snippet"))
            print(f"{names[cid][:36]:<38}{len(found):>5}  avg {mean:.2f}  "
                  + " ".join(f"{n}★{stars[n]}" for n in (5, 4, 3, 2, 1))
                  + f"  with text {text}  {found[-1]['isoDate'][:10]} to {found[0]['isoDate'][:10]}")
  • MAX_PAGES is a relevance setting as much as a cost one. Five pages is up to 100 places for 50 credits. Raise it for a complete sweep and filter harder.
  • Reviews stop at a date. They come newest first, so since ends the paging at the first older review. A place with nothing new costs one call.
  • The review CSV has no names. Each review comes with its author's name and profile link. The script keeps the rating, date, text and ID, which is what counting and reading need; add columns to REVIEW_FIELDS only if you have a use for them.
  • Hours are tidied. Google writes times with a narrow no-break space ("7 AM"), which looks right and breaks string matching, so the script swaps it for a plain space.
  • A failure keeps what was fetched. Concurrency limits (429), server errors and dropped connections are retried with a growing pause. When a call still fails, or the credits run out (402), the script writes the pages it already has, says where it stopped and, for a single place, prints the command that carries on from the next page. Failed calls are not charged.
  • No cell can run as a formula. Review text and business names are written by strangers, and a spreadsheet treats a cell that starts with =, +, - or @ as a formula. cell() puts an apostrophe in front of such text so Excel or Sheets shows it instead. International phone numbers (+44…) get one too; numbers such as the longitude are left alone.

Run it: a list of roasters

The search, and a word every kept place must have in its name or categories:

python maps.py places "coffee roasters in portland oregon" roast
95 places, 49 kept, 50 credits
with a phone: 36, with a website: 48
Stumptown Coffee Roasters              4.4  1,912  (503) 295-6144  4468633830792467229
Coava Coffee Roasters                  4.6  1,705  (503) 894-8134  14283988993230715565
Case Study Coffee Roasters             4.4  1,461  (503) 477-8221  8317811742865840478
Proud Mary Cafe                        4.5  1,240  (503) 208-3475  17324691347336370546
Rose City Coffee Co.                   4.7  1,181  (503) 956-2211  16751193622318556372
Stumptown Coffee Roasters              4.5  1,097  (855) 711-3385  12467283892523634120
Sterling Coffee Roasters               4.8    839                  9801327684547407909
Upper Left Roasters                    4.6    771  (503) 477-8469  1928717419902656531

Five calls, seven seconds. Of 95 places, 49 were roasters by name or category, and the printout shows the eight with the most ratings. Stumptown is there twice because it has several cafes; each is its own place with its own cid. places.csv has every kept place:

title,type,address,phoneNumber,website,rating,ratingCount,latitude,longitude,cid,placeId,hours
Sterling Coffee Roasters,Coffee shop,"518 NW 21st Ave, Portland, OR 97209",,http://sterling.coffee/,4.8,839,45.526635999999996,-122.69422379999999,9801327684547407909,ChIJ726ePPcJlVQRJXCmdJlPBYg,Mon 6:30 AM–5 PM; Tue 6:30 AM–5 PM; Wed 6:30 AM–5 PM; Thu 6:30 AM–5 PM; Fri 6:30 AM–5 PM; Sat 7:30 AM–5 PM; Sun 7:30 AM–5 PM
Roseline Coffee Cafe & Roastery,Coffee shop,"321 NE Davis St, Portland, OR 97232",(503) 809-2890,http://roselinecoffee.com/,4.7,210,45.5245982,-122.662453,955739314569279103,ChIJ8WaP9JShlVQRf_bGxtd3Qw0,Mon 7 AM–5 PM; Tue 7 AM–5 PM; Wed 7 AM–5 PM; Thu 7 AM–5 PM; Fri 7 AM–5 PM; Sat 7 AM–5 PM; Sun 7 AM–5 PM
…

There is no email column because Maps listings have none. The website column is the way to one: fetch each site's contact page with the scrape endpoint, or let AI extraction pull the address out as a field.

Run it: reviews, all or new

Every review of one place, by its cid from the list:

python maps.py reviews 3336897143139170785
101 reviews for 1 places, 60 credits
3336897143139170785                     101  avg 4.73  5★91 4★3 3★1 2★2 1★4  with text 71  2024-05-28 to 2026-11-07
cid,isoDate,rating,likes,snippet,id
3336897143139170785,2026-11-07T18:53:26.877Z,5,0,"They have free wifi, a few food items, good drinks, 6 2top tables, good music, a few outlets and 2 bathrooms. There's a few steps to walk up for accessibility and overall the place is small and cozy.",Ci9DQUlRQUNvZENodHljRjlvT2pJM1R6aDFURlpGTVUxUFFrWnJXbDl5VDNWdlQwRRAB
3336897143139170785,2026-10-24T23:46:52.015Z,5,0,Great coffee and for some reason I get a cool retro 80's vibe when I go there.,Ci9DQUlRQUNvZENodHljRjlvT2t0clkyRXdWbkp0VDB0c0xVOVBUMkpRVUd0WVpHYxAB
3336897143139170785,2026-10-14T23:17:53.018Z,2,0,,Ci9DQUlRQUNvZENodHljRjlvT25sSlEyVXRlRUZNUjFFeVVtSTNiVlF5ZG5OWmNXYxAB
…

That was six calls. Run it on the free plan straight after the places search and only five are left; the sixth is refused. We set an account to 50 credits and ran the same command:

3336897143139170785: stopped after 100 reviews: 402 Insufficient credits. Please upgrade your plan.
  to continue: python maps.py reviews 3336897143139170785 "" CjEIARIpCgoAP72HAqI0eq__EhAzTRaT7h8d17FRKWoAAAAAGgn98aUCcCojR6oYACIA
100 reviews for 1 places, 50 credits
3336897143139170785                     100  avg 4.73  5★90 4★3 3★1 2★2 1★4  with text 70  2024-06-01 to 2026-11-07

The hundred reviews it had paid for are in reviews.csv. The last argument of the printed command is the cursor for the page that failed (the empty quotes stand for "no date"). With credits again, that command fetched the one remaining review for 10 credits and added it to the file: 101 rows, none twice. A cursor we had saved 42 minutes earlier still returned the same page, so there is time to top up.

Or what is new across the whole list. Give it the CSV and a date:

python maps.py reviews places.csv 2026-10-01
267 reviews for 49 places, 500 credits
Sterling Coffee Roasters                 15  avg 4.87  5★13 4★2 3★0 2★0 1★0  with text 10  2026-10-04 to 2026-11-07
Portland Coffee Roasters                  2  avg 3.50  5★1 4★0 3★0 2★1 1★0  with text 2  2026-10-13 to 2026-10-19
Roseline Coffee Cafe & Roastery          10  avg 4.10  5★7 4★0 3★1 2★1 1★1  with text 8  2026-10-04 to 2026-11-06
Coava Coffee Roasters                    11  avg 4.64  5★8 4★2 3★1 2★0 1★0  with text 6  2026-10-12 to 2026-11-10
Superjoy Coffee                           4  avg 4.75  5★3 4★1 3★0 2★0 1★0  with text 4  2026-10-06 to 2026-11-02
Autumn Coffee Roasting                    4  avg 4.75  5★3 4★1 3★0 2★0 1★0  with text 3  2026-10-20 to 2026-11-08
…

Forty days of reviews for 49 roasters: 267 reviews in 50 calls and 37 seconds, one call per place except the one that had more than 20 new reviews. Roseline's 4.7 overall was a 4.10 over these weeks; that gap between the lifetime average and the recent one is what a monthly run is for. Schedule it with cron and pass the date of the last run:

0 7 1 * * cd ~/maps && python3 maps.py reviews places.csv $(date -d "1 month ago" +\%F) >> reviews.log 2>&1

(That is GNU date, as on Linux; on macOS use date -v-1m +%F.)

Cost and speed

  • 10 credits a call, half a credit a row. A page is 20 places or 20 reviews. A thousand places is about 50 calls: 500 credits, roughly 9 cents on Starter ($49.99 for 275,000 credits). All 839 of Sterling's reviews were 420 credits.
  • Empty answers are charged, failures are not. A page past the end and a cid with no reviews are successful calls. A refused request (400), a timeout and a server error cost nothing.
  • About a second a page. Places pages took 1.0 to 2.4 seconds and review pages 0.4 to 2.1. Paging through one search or one place's reviews is one call after another by nature; reviews for a list of places run side by side up to CONCURRENCY.
  • On the free plan: 100 credits is 10 calls. The places search above took five, which leaves five pages of reviews: the newest 100 of one place, or the newest 20 of five.

Where to go from here

Common questions

Is there an official Google Maps API for places and reviews?

Yes, the Places API, with two limits that matter here: Text Search "returns a maximum of 60 results across all pages", and Place Details returns "up to five" reviews per place (checked November 2026). When you need the whole list for a query, or every review of a place, you need what the Maps site itself shows. POST {"endpoint": "/maps", "params": {"q": "<query>"}} to https://scrapingbot.io/api/v1/google for places and {"endpoint": "/reviews", "params": {"cid": "<cid>"}} for reviews.

How many places can I get for one Google Maps search?

Twenty a page for as long as Google has results. For "coffee roasters in portland oregon" that was 8 pages: 155 results, 150 different places. Pages after the first need the ll viewport that page 1 returns. Later pages drift away from the query, so filter on types or the name.

Can I get all the reviews of a place, not just five?

Yes. /reviews returns 20 a page with a nextPageToken for the next. A place showing 839 reviews came back as 839 reviews in 42 calls with no duplicates, in about 30 seconds; one showing 101 came back as 101 in 6 calls. Use sortBy: "newest" and stop at a date to fetch only what is new.

Does it return phone numbers, websites and emails?

Phone numbers and websites when the listing has them: of 150 places, 106 had a phoneNumber and 138 a website. A missing field is left out of the place, not set to null. Google Maps listings carry no email address; the website is where to look for one.

What is the difference between the /places and /maps endpoints?

/places returns 10 results a page with name, street address, category, rating, website and coordinates. /maps returns 20 a page and adds the phone number, opening hours, full address, all the place's categories, placeId and fid. Both cost 10 credits a call, so /maps is the one to use for a list of businesses.

How much does it cost?

Ten credits a call, and a call is a page of 20 places or 20 reviews: half a credit each. A thousand places is about 50 calls, 500 credits, roughly 9 cents on the Starter plan ($49.99 for 275,000 credits). The 100 free credits cover 10 calls: 200 places, or 100 places and 100 reviews.

Start scraping in the next five minutes.

100 free credits, no credit card. One API key works for websites, TikTok, Instagram, Google, Amazon and ChatGPT.