{
  "openapi": "3.0.1",
  "info": {
    "title": "Google Maps Reviews Scraper — Date Range, New-Only, Complete",
    "description": "Google Maps reviews by place id, URL, short link or name. One row per review: stars, text, owner replies, exact dates, translation, photos, reviewer. Free place row: rating, review count, completeness check. Free filters: date range, only new reviews, negative reviews, text-only, keywords, language.",
    "version": "1.0",
    "x-build-id": "l0pPgzYjZgZ1rlaRp"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/kestrel~google-maps-reviews-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-kestrel-google-maps-reviews-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/kestrel~google-maps-reviews-scraper/runs": {
      "post": {
        "operationId": "runs-sync-kestrel-google-maps-reviews-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/kestrel~google-maps-reviews-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-kestrel-google-maps-reviews-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "places": {
            "title": "Places",
            "uniqueItems": true,
            "type": "array",
            "description": "One place per line, any mix: a Google Maps place URL, a maps.app.goo.gl or share.google short link, a place id (\"ChIJD3uTd9hx5kcR1IQvGfr8dbk\"), a CID, a feature id, a Maps search URL, or just \"Katz's Delicatessen, New York\". A place Google does not know ends as a free not_found status row, never a failed run. A chain name can resolve to another branch: for repeat runs pass the URL, place id or CID. The same place given twice is read once; a pasted spreadsheet column works as it is.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "maxReviewsPerPlace": {
            "title": "Max reviews per place",
            "minimum": 0,
            "maximum": 1000000,
            "type": "integer",
            "description": "Reviews delivered and charged per place, counted after filters: 100 (default), 0 = every review the place has. The main cost control: 100 reviews × 50 places is at most 5,000 reviews = $2.00. Use 0 with fromDate for \"every review since 1 July\", 0 alone for a full backup. Filters never count against it: 50 asked with onlyWithText = 50 reviews with text.",
            "default": 100
          },
          "fromDate": {
            "title": "From date",
            "type": "string",
            "description": "Only reviews published on or after this day: \"2026-07-01\" (YYYY-MM-DD) or a look-back such as \"30 days\", \"2 weeks\", \"6 months\". Empty (default) = no lower bound. With sort newest the actor stops reading at this date, so \"reviews from the last 30 days\" of a place with 50,000 reviews reads one page, not a thousand, and only reviews in range are billed. Use a look-back for scheduled runs: the input never needs editing.",
            "default": ""
          },
          "toDate": {
            "title": "To date",
            "type": "string",
            "description": "Only reviews published on or before this day: \"2026-09-30\" (YYYY-MM-DD) or a look-back such as \"7 days\". Empty (default) = no upper bound. With fromDate it is a true date range, e.g. one calendar quarter; reviews newer than toDate are read past free. Dates compare with published_at (first posted); set includeEdited true to also count a review edited inside the range.",
            "default": ""
          },
          "sort": {
            "title": "Order",
            "enum": [
              "newest",
              "most_relevant",
              "highest",
              "lowest"
            ],
            "type": "string",
            "description": "\"newest\" (default), \"most_relevant\" (Google's own order), \"highest\", \"lowest\". Keep newest for date ranges and monitoring: the walk stops at fromDate, so nothing older is read or paid for. \"lowest\" answers \"the complaints about this place\" fastest; combine it with maxStars 2. With onlyNew the order is always newest.",
            "default": "newest"
          },
          "onlyNew": {
            "title": "Only new reviews since the last run",
            "type": "boolean",
            "description": "false (default) = every run delivers what the other inputs ask for. true = monitoring: the actor remembers which reviews it delivered for each place (memory named by monitorKey) and later runs deliver and bill only reviews not delivered before, stopping at the first page of already-delivered ones. Put it on a schedule for \"new Google reviews every week\"; a run that finds nothing new delivers 0 rows and costs $0. The first run delivers up to maxReviewsPerPlace per place. Sort is forced to newest.",
            "default": false
          },
          "monitorKey": {
            "title": "Monitor key",
            "type": "string",
            "description": "Name of the memory onlyNew uses, e.g. \"acme-weekly\" (default \"default\"; letters, digits and hyphens, up to 40). Runs with the same key share one memory: give each separate feed (a client, a Slack channel, a test) its own key so they do not hide reviews from each other. Ignored unless onlyNew is true.",
            "default": "default"
          },
          "includeEdited": {
            "title": "Also deliver edited reviews",
            "type": "boolean",
            "description": "false (default) = a review is delivered once, as first seen. true = with onlyNew, a review whose author edited it after you received it is delivered (and billed) again with edited = true and both dates; with a date range, a review edited inside the range counts as inside it.",
            "default": false
          },
          "minStars": {
            "title": "Min stars",
            "minimum": 0,
            "maximum": 5,
            "type": "integer",
            "description": "Keep reviews with at least this many stars, 1 to 5, e.g. 4 for positive reviews only. 0 (default) = no lower bound. Free: filtered reviews are never charged.",
            "default": 0
          },
          "maxStars": {
            "title": "Max stars",
            "minimum": 0,
            "maximum": 5,
            "type": "integer",
            "description": "Keep reviews with at most this many stars, e.g. 2 for negative reviews and complaints only, 3 for \"everything below 4 stars\". 0 (default) = no upper bound. Free; pair it with sort \"lowest\" to reach them first.",
            "default": 0
          },
          "onlyWithText": {
            "title": "Only reviews with text",
            "type": "boolean",
            "description": "false (default) = star-only ratings are delivered too. true = skip them: about a third of Google reviews carry no text, and with this on they are read past free, so you pay only for reviews that say something — what sentiment analysis and AI summaries need. maxReviewsPerPlace then counts only reviews with text.",
            "default": false
          },
          "hasOwnerReply": {
            "title": "Owner reply",
            "enum": [
              "any",
              "yes",
              "no"
            ],
            "type": "string",
            "description": "\"any\" (default), \"yes\" = only reviews the owner replied to, \"no\" = only reviews still waiting for a reply. \"no\" is a reputation manager's to-do list (\"which reviews have we not answered\"); \"yes\" shows a competitor's reply tone and speed (owner_reply_at). Free.",
            "default": "any"
          },
          "keywords": {
            "title": "Keywords (any of)",
            "type": "array",
            "description": "Keep a review only if its text contains at least one of these words or phrases, case-insensitive, e.g. [\"parking\", \"wait time\"]. Empty (default) = no keyword filter. Matched in the original text and, with translate \"both\", in Google's translation too. Free.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "excludeKeywords": {
            "title": "Exclude keywords",
            "type": "array",
            "description": "Drop a review if its text contains any of these words or phrases, e.g. [\"delivery\"]. Empty (default) = nothing dropped. Case-insensitive, matched like keywords. Free.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "reviewLanguages": {
            "title": "Review languages",
            "type": "array",
            "description": "Keep only reviews written in these languages, as two-letter codes: [\"en\"] or [\"de\", \"fr\"]. Empty (default) = every language. Uses the language Google detected (original_language); star-only ratings have no language and are dropped when this is set. Free.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "hasPhotos": {
            "title": "Only reviews with photos",
            "type": "boolean",
            "description": "false (default) = all reviews. true = only reviews that carry at least one photo (URLs in photos). Free.",
            "default": false
          },
          "translate": {
            "title": "Text",
            "enum": [
              "original",
              "translated",
              "both"
            ],
            "type": "string",
            "description": "\"original\" (default) = text as written, text_translated empty. \"translated\" = text holds Google's translation into language (reviews already in that language are unchanged). \"both\" = text original, text_translated the translation. Use \"translated\" or \"both\" for \"all reviews in English\" or sentiment analysis across languages. The owner reply follows the same choice. Free.",
            "default": "original"
          },
          "language": {
            "title": "Language",
            "type": "string",
            "description": "Interface language as a code: \"en\" (default), \"de\", \"fr\", \"es\", \"pt-BR\", \"ja\". Sets the language of translations (translate), of place categories and of Google's relative dates (published_text). Exact dates (published_at, updated_at) never depend on it.",
            "default": "en"
          },
          "country": {
            "title": "Country",
            "type": "string",
            "description": "Two-letter country Google is asked from (gl): \"us\" (default), \"gb\", \"de\", \"jp\". Rarely matters: change it only when a plain name resolves to a branch in the wrong country. It does not filter reviews.",
            "default": "us"
          },
          "includePlaceRow": {
            "title": "Place summary row",
            "type": "boolean",
            "description": "true (default) = one free type \"place\" row per place: Google's rating, listed review count, per-star histogram, how many reviews this run read and delivered, the completeness verdict (\"4,404 listed, 4,404 read — complete\"), what stopped the walk early, and a 90-day rating trend when the walk covered 180 days. For \"rating and review count only\" set maxReviewsPerPlace 1: one review is billed, the place row is free. false = review rows only.",
            "default": true
          },
          "placeIds": {
            "title": "Place ids (same as Places)",
            "type": "array",
            "description": "Alias for places that takes Google place ids only, e.g. [\"ChIJD3uTd9hx5kcR1IQvGfr8dbk\"], so the placeIds list of a compass/google-maps-reviews-scraper input or of a Google Places API export pastes straight in. Read exactly like lines in places; the same place in both is read once.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "startUrls": {
            "title": "Start URLs (same as Places)",
            "type": "array",
            "description": "Alias for places, for integrations that always send startUrls (Make, n8n, Zapier, a Google Sheet of links) and for input copied from compass/google-maps-reviews-scraper, e.g. [{\"url\": \"https://www.google.com/maps/place/…\"}]. Each entry is read exactly like a line in places. Compass names maxReviews, reviewsSort (newest, mostRelevant, highestRanking, lowestRanking), reviewsStartDate and language are understood too.",
            "default": [],
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL of a web page",
                  "format": "uri"
                }
              }
            }
          },
          "sessions": {
            "title": "Parallel sessions",
            "minimum": 1,
            "maximum": 16,
            "type": "integer",
            "description": "Proxy sessions (IPs) working at once, 1 to 16, default 4. Places are read in parallel; the pages of one place in order. Raise it for hundreds of places.",
            "default": 4
          },
          "perIp": {
            "title": "Requests per second per IP",
            "type": "string",
            "description": "Requests per second per session, e.g. \"1\" (default) or \"0.5\". Lower is gentler on Google; 50 reviews come back per request.",
            "default": "1"
          },
          "proxyConfiguration": {
            "title": "Proxy",
            "type": "object",
            "description": "Apify datacenter proxies (default {\"useApifyProxy\": true}) are enough and keep the run cheap; residential is not needed. The actor rotates the IP whenever Google answers an empty or cut-down page.",
            "default": {
              "useApifyProxy": true
            }
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}