{
  "openapi": "3.0.1",
  "info": {
    "title": "Google Maps Leads Scraper",
    "description": "Extract qualified B2B leads from Google Maps by search query and location. Returns business name, address, phone, website, rating, review count, category, coordinates, and emails harvested from the linked website. Built for outbound sales and territory research.",
    "version": "0.2",
    "x-build-id": "geKMcyoF0OoyWmOu9"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/hasnainnisar67~google-maps-leads-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-hasnainnisar67-google-maps-leads-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/hasnainnisar67~google-maps-leads-scraper/runs": {
      "post": {
        "operationId": "runs-sync-hasnainnisar67-google-maps-leads-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/hasnainnisar67~google-maps-leads-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-hasnainnisar67-google-maps-leads-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": {
          "prompt": {
            "title": "Natural-language prompt (optional)",
            "type": "string",
            "description": "Describe what you want in plain English — the actor parses it into a structured query, location, lead count, and filter set. Examples: \"find 50 dentists in Dubai with email\", \"plumbers in Karachi Pakistan rating above 4\", \"100 coffee shops in NYC no website\". When provided, the parsed values OVERRIDE `searchTerms`/`location`/`maxResults`/individual filters/`deepScan` unless those are also explicitly set. AI parsing uses the actor's pre-configured Groq API key; a rule-based fallback parser kicks in if it's missing."
          },
          "searchTerms": {
            "title": "Search terms",
            "type": "array",
            "description": "One or more search queries to run on Google Maps. Each term is searched independently and results are merged into one dataset. Combined with `placeCategories` if both are set. Ignored when `prompt` is set, or when `placeUrls`/`placeIds` are supplied.",
            "items": {
              "type": "string"
            }
          },
          "placeCategories": {
            "title": "Place categories (multiply with search terms)",
            "type": "array",
            "description": "Optional category list — the actor expands each searchTerm × category combination. Example: searchTerms=[\"luxury\"], placeCategories=[\"hotels\",\"restaurants\"] → searches \"luxury hotels\" and \"luxury restaurants\". Leave empty to use search terms verbatim.",
            "items": {
              "type": "string"
            }
          },
          "placeUrls": {
            "title": "Google Maps URLs (alternative to search)",
            "type": "array",
            "description": "Direct Maps URLs to scrape. RECOMMENDED FORMAT: `https://www.google.com/maps/place/?q=place_id:<id>` — works reliably. The `/maps/place/<Name>/data=!4m7!...` form that Google's 'Share → Copy link' produces is unreliable from headless browsers (Maps doesn't fully render the detail pane on cold-start). If you have one of those, prefer using `placeIds` below — the actor converts those to the working URL form automatically.",
            "items": {
              "type": "string"
            }
          },
          "placeIds": {
            "title": "Place IDs (alternative to search)",
            "type": "array",
            "description": "Google Place IDs (e.g. `ChIJN1t_tDeuEmsRUsoyG83frY4`). Each is converted to a Maps URL and scraped exactly like `placeUrls`. Combine freely with `placeUrls`.",
            "items": {
              "type": "string"
            }
          },
          "location": {
            "title": "Location (free-text)",
            "type": "string",
            "description": "City, region, or country to scope the search to. Combined with each search term as \"<term> in <location>\". Leave empty to fall back to the structured Country/City/State fields below."
          },
          "country": {
            "title": "Country",
            "type": "string",
            "description": "Used only when free-text Location above is empty. Combined with City/State/County/Postal code into a single location string."
          },
          "city": {
            "title": "City",
            "type": "string",
            "description": "Used only when free-text Location is empty."
          },
          "state": {
            "title": "State / Province",
            "type": "string",
            "description": "Used only when free-text Location is empty."
          },
          "county": {
            "title": "County",
            "type": "string",
            "description": "Used only when free-text Location is empty. Rarely needed but kept for parity with admin hierarchies."
          },
          "postalCode": {
            "title": "Postal / ZIP code",
            "type": "string",
            "description": "Used only when free-text Location is empty."
          },
          "maxResults": {
            "title": "Max results per search term",
            "minimum": 1,
            "maximum": 500,
            "type": "integer",
            "description": "When filters are off, this is the search cap. When filters are active, the actor over-discovers (up to 3×, capped at 25 URLs) and stops scraping the moment enough records pass — so this becomes an output target.",
            "default": 50
          },
          "language": {
            "title": "Language",
            "enum": [
              "en",
              "es",
              "fr",
              "de",
              "it",
              "pt",
              "nl",
              "pl",
              "ru",
              "ja",
              "ko",
              "zh-CN",
              "ar",
              "hi"
            ],
            "type": "string",
            "description": "Two-letter language code passed to Google Maps via the `hl` parameter. Affects business names, categories, and review content.",
            "default": "en"
          },
          "deepScan": {
            "title": "Deep scan — visit each business website to harvest contact details",
            "type": "boolean",
            "description": "ON by default. The actor visits every listing's website (homepage + a couple of contact-style sub-pages) to harvest emails, phone numbers, social-media links, and named contacts. The best email/phone is also promoted into the top-level `email` / `phone` fields. Adds ~3-8 seconds per listing that has a website.",
            "default": true
          },
          "extractEmails": {
            "title": "Quick email lookup (used only when Deep scan is OFF)",
            "type": "boolean",
            "description": "Lightweight fallback for when Deep scan is disabled: fetches the homepage and standard contact paths over plain HTTP (no browser) looking for an email. Ignored when Deep scan is on, since deep scan is strictly more thorough.",
            "default": false
          },
          "extractWebsiteFallback": {
            "title": "Try `/contact` if homepage has no email",
            "type": "boolean",
            "description": "Only used by Quick email lookup (when Deep scan is OFF). Doubles per-site latency for sites that don't expose an email at all.",
            "default": true
          },
          "verifyEmails": {
            "title": "Verify discovered emails (MX-record check)",
            "type": "boolean",
            "description": "Runs a syntax check + DNS MX-record lookup on every email the actor produces. Adds an `emailValid` boolean to each record. Adds ~50-200 ms per unique domain (cached). Recommended for cold-outreach lists.",
            "default": false
          },
          "reviewsLimit": {
            "title": "Reviews to extract per place",
            "minimum": 0,
            "maximum": 1000,
            "type": "integer",
            "description": "0 = skip reviews scraping entirely. Up to ~200 per place is reasonable; higher counts substantially increase per-place runtime (≈1 s per 10 reviews scrolled). Output appears in a `reviews` array on each record.",
            "default": 0
          },
          "reviewsSort": {
            "title": "Sort reviews by",
            "enum": [
              "newest",
              "mostRelevant",
              "highestRating",
              "lowestRating"
            ],
            "type": "string",
            "description": "How Maps orders the reviews list before we read it. `newest` is best for tracking; `mostRelevant` is the Maps default; `highestRating`/`lowestRating` for sentiment slicing.",
            "default": "newest"
          },
          "reviewsSince": {
            "title": "Only reviews posted after this date (YYYY-MM-DD)",
            "type": "string",
            "description": "Optional cutoff. Reviews older than this date are dropped. Maps shows reviews as relative time (\"3 weeks ago\") so absolute dates are estimated; off-by-a-day is possible."
          },
          "reviewsKeywords": {
            "title": "Filter reviews by keywords (any-match, comma-separated)",
            "type": "string",
            "description": "If set, only reviews whose text contains at least one of these terms (case-insensitive) are kept. Empty = no keyword filter."
          },
          "includeReviewerData": {
            "title": "Include reviewer profile data with each review",
            "type": "boolean",
            "description": "When on, each review carries the reviewer's display name, profile URL, and total-reviews count where Maps exposes them.",
            "default": true
          },
          "imagesLimit": {
            "title": "Additional images to extract per place",
            "minimum": 0,
            "maximum": 200,
            "type": "integer",
            "description": "0 = main photo only (free, always included as `mainImageUrl`). Higher numbers open the photo gallery and harvest URLs. Output appears in an `images` array on each record. The first ~30 images load fast; beyond that requires extra scrolling.",
            "default": 0
          },
          "includeImageAuthors": {
            "title": "Include uploader name with each image",
            "type": "boolean",
            "description": "Adds the photo uploader's display name to each image entry where Maps shows it.",
            "default": false
          },
          "noWebsite": {
            "title": "Filter: only listings WITHOUT a website",
            "type": "boolean",
            "description": "Keep only records whose `website` field is empty.",
            "default": false
          },
          "hasWebsite": {
            "title": "Filter: only listings WITH a website",
            "type": "boolean",
            "description": "Mutually exclusive with the previous toggle.",
            "default": false
          },
          "hasPhone": {
            "title": "Filter: must have a phone number",
            "type": "boolean",
            "description": "Drop records with no phone (Google Maps `phone` field, or `deepScanPhones` if Deep scan is on).",
            "default": false
          },
          "hasEmail": {
            "title": "Filter: must have an email",
            "type": "boolean",
            "description": "Drop records with no email. Without Deep scan, only the Quick-email-lookup result is checked.",
            "default": false
          },
          "hasWhatsapp": {
            "title": "Filter: must have a WhatsApp link",
            "type": "boolean",
            "description": "Keep only records whose deep-scanned social links include `wa.me` or `whatsapp.com`. Effectively requires Deep scan.",
            "default": false
          },
          "skipClosedPlaces": {
            "title": "Filter: skip permanently / temporarily closed places",
            "type": "boolean",
            "description": "Drop records flagged with \"Permanently closed\" or \"Temporarily closed\" on Maps.",
            "default": false
          },
          "exactNameMatch": {
            "title": "Filter: only places whose name contains the search term",
            "type": "boolean",
            "description": "Maps's relevance ranking can return loosely-related places. Enable this to drop records whose `name` doesn't contain at least one of the search terms (case-insensitive). No effect when running from `placeUrls`/`placeIds`.",
            "default": false
          },
          "minRating": {
            "title": "Filter: minimum rating",
            "minimum": 0,
            "maximum": 5,
            "type": "number",
            "description": "Drop records with rating below this value. Records with no rating at all are also dropped. Leave blank for no minimum."
          },
          "maxRating": {
            "title": "Filter: maximum rating",
            "minimum": 0,
            "maximum": 5,
            "type": "number",
            "description": "Drop records with rating above this value. Leave blank for no maximum."
          },
          "minReviews": {
            "title": "Filter: minimum review count",
            "minimum": 0,
            "type": "integer",
            "description": "Drop records with fewer reviews than this. Leave blank for no minimum."
          },
          "headless": {
            "title": "Run browser headless",
            "type": "boolean",
            "description": "Disable only for debugging on a local run — Apify's servers are headless regardless.",
            "default": true
          },
          "proxy": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Use Apify Proxy or your own custom proxies. Strongly recommended — Google rate-limits scrapers from any single IP after a few hundred requests. RESIDENTIAL group is safest.",
            "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
                  }
                }
              },
              "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}