{
  "openapi": "3.0.1",
  "info": {
    "title": "Local Business Leads Scraper: Verified Business Emails & Finder",
    "description": "Local business leads scraper, local business email finder and local business email scraper: any category, any city. Verified business emails (MX-checked), phones, socials on every row - local leads with emails, no API key, no proxy, $3 per 1,000. Business email finder for agencies.",
    "version": "0.1",
    "x-build-id": "GtkZGHfWgmuROjuM3"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/flash_scraper~local-business-leads/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-flash_scraper-local-business-leads",
        "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/flash_scraper~local-business-leads/runs": {
      "post": {
        "operationId": "runs-sync-flash_scraper-local-business-leads",
        "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/flash_scraper~local-business-leads/run-sync": {
      "post": {
        "operationId": "run-sync-flash_scraper-local-business-leads",
        "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",
        "required": [
          "category",
          "location"
        ],
        "properties": {
          "preset": {
            "title": "Use-case preset",
            "enum": [
              "",
              "cold_email",
              "web_design",
              "call_list",
              "full_enrichment"
            ],
            "type": "string",
            "description": "Pick one job and the rest of the form configures itself. A preset only fills in settings you left at their default value — <b>anything you set yourself always wins</b> — and the run log names the preset and lists exactly which settings it applied and which it skipped because you had already chosen them.<br><br><b>Cold-email list</b> — only businesses with an MX-verified, deliverable email.<br><b>Web-design prospects</b> — the thin/neglected half of the market (lead score at or below 45): no website at all, or a website that publishes no email and no marketing tech. It also requires at least one way to contact the business, because a score cap on its own selects the rows with no phone, no email and no socials — the ones you cannot pitch.<br><b>Call list</b> — only businesses with a phone number.<br><b>Full enrichment</b> — every enrichment signal on, 6 pages crawled per website, plus the opt-in guessed-email column.<br><br>A preset only ever <i>adds</i> filters or enrichment; it never removes a filter you set and never causes a business to be billed that your own settings exclude. Leave it on <code>Custom</code> to change nothing at all.",
            "default": ""
          },
          "categorySelect": {
            "title": "Business category",
            "enum": [
              "",
              "dentist",
              "plumber",
              "electrician",
              "roofing contractor",
              "restaurant",
              "cafe",
              "gym",
              "hair salon",
              "auto repair",
              "lawyer",
              "doctor",
              "pharmacy",
              "veterinary",
              "hotel",
              "real estate agency",
              "dry cleaner",
              "landscaper",
              "pest control",
              "photographer",
              "moving company"
            ],
            "type": "string",
            "description": "Pick a vertical whose OpenStreetMap tag has been verified end to end — <code>Dentist</code> maps to <code>amenity=dentist</code> + <code>healthcare=dentist</code>. Leave it on <b>Custom</b> to type anything you like in the box below: more than 200 terms are mapped, including <code>medspa</code>, <code>HVAC contractor</code>, <code>realtor</code> and <code>exterminator</code>.<br><br>Precedence: <b>Multiple categories</b> &gt; this dropdown &gt; the custom box.",
            "default": ""
          },
          "category": {
            "title": "Custom category",
            "type": "string",
            "description": "Type the trade in plain English — <code>medspa</code>, <code>HVAC contractor</code>, <code>funeral home</code>, <code>photographer</code>. Used only while the dropdown above is on <b>Custom</b>. Common terms map to exact OpenStreetMap tags; unrecognised terms fall back to a broad tag guess PLUS a business-name match, and a mapped category with zero tagged places in the area is retried by business name too (businesses found only by name are labelled <code>category_match='name_keyword'</code> so you can filter them). OpenStreetMap coverage varies hugely by category: storefront businesses (dentists, salons, restaurants, shops) are dense, while van-based trades (plumbers, electricians, roofers) can return single digits even in a big city. A term matching nothing returns 0 businesses with a suggestion and no charge.",
            "default": "dentist"
          },
          "categories": {
            "title": "Multiple categories",
            "type": "array",
            "description": "Add SEVERAL categories to cover in one run — <code>dentist</code> + <code>orthodontist</code> + <code>dental clinic</code>. Every category is crossed with every location below (a matrix), the businesses are merged, and the same business found by two categories is delivered and billed <b>once</b> (deduplicated on its OpenStreetMap object id). Leave empty to use the dropdown or the custom box above — when this list is non-empty it beats both. Max 25 category x location combinations per run.",
            "items": {
              "type": "string"
            }
          },
          "citySelect": {
            "title": "City",
            "enum": [
              "",
              "New York, NY",
              "Los Angeles, CA",
              "Chicago, IL",
              "Houston, TX",
              "Phoenix, AZ",
              "Philadelphia, PA",
              "San Antonio, TX",
              "San Diego, CA",
              "Dallas, TX",
              "Austin, TX",
              "Miami, FL",
              "Atlanta, GA",
              "Boston, MA",
              "Seattle, WA",
              "Denver, CO",
              "London, United Kingdom",
              "Paris, France",
              "Berlin, Germany",
              "Madrid, Spain",
              "Amsterdam, Netherlands",
              "Toronto, Canada",
              "Sydney, Australia",
              "Dubai, United Arab Emirates",
              "Casablanca, Morocco",
              "Rabat, Morocco"
            ],
            "type": "string",
            "description": "Pick a metro that has been geocode-verified against OpenStreetMap, or leave it on <b>Custom</b> and type any city on earth in the box below — <code>Lyon, France</code>, <code>Marrakesh, Morocco</code>.<br><br>Precedence: <b>Multiple cities</b> &gt; this dropdown &gt; the custom box.",
            "default": ""
          },
          "location": {
            "title": "Custom location",
            "type": "string",
            "description": "Type the city with its region or country — <code>Austin, Texas</code>, <code>Lyon, France</code>, <code>Casablanca, Morocco</code>. Used only while the dropdown above is on <b>Custom</b>. Capitalisation and the comma are optional: <code>RABAT MAROC</code> and <code>casablanca morocco</code> resolve too, because the raw string is tried first and then automatically re-spelled (<code>Rabat Maroc</code>, <code>RABAT, MAROC</code>, <code>Rabat, Maroc</code>) before the run is given up on.",
            "default": "Austin, Texas"
          },
          "locations": {
            "title": "Multiple cities",
            "type": "array",
            "description": "Add SEVERAL cities to cover in one run — <code>Austin, Texas</code> + <code>Dallas, Texas</code>. Each city is crossed with each category above, and the <b>Max businesses</b> budget is shared fairly between the searches (businesses are interleaved, so city 1 cannot eat the whole quota). Leave empty to use the dropdown or the custom box above — when this list is non-empty it beats both. Every business carries <code>query_category</code> and <code>query_location</code> so you can tell which search produced it. Max 25 category x location combinations per run. API callers: send a JSON array; a plain string is split on newlines and semicolons only, never on commas, so <code>Austin, Texas</code> stays one city.",
            "items": {
              "type": "string"
            }
          },
          "countryCode": {
            "title": "Country code",
            "type": "string",
            "description": "Pin the geocoder to one country with an ISO 3166-1 alpha-2 code — <code>ma</code> (Morocco), <code>us</code>, <code>fr</code>, <code>gb</code>. Use it when your city name exists in several countries: <i>Rabat</i> is a city in both Morocco and Malta, <i>Cambridge</i> in both the UK and the USA. Comma-separated codes are allowed (<code>gb,ie</code>). Leave empty for a worldwide search. The pin is enforced: if the city cannot be resolved inside that country the run stops with a clear message and no charge, rather than falling back to a worldwide lookup and delivering a same-named city somewhere else."
          },
          "keyword": {
            "title": "Name keyword",
            "type": "string",
            "description": "Keep only businesses whose NAME contains this word — <code>smile</code> narrows dentists to <i>Smile Studio</i> and <i>Bright Smiles</i>. Since 2026-08-29 it is also matched against the OpenStreetMap <code>cuisine</code> and <code>healthcare:speciality</code> tags, so <code>sushi</code> returns the sushi restaurants whose name never says sushi and <code>orthodontics</code> the dentists tagged with that speciality. Case-insensitive and matched anywhere in the value; the address, website and email are never matched. Leave empty to keep every business in the category."
          },
          "maxItems": {
            "title": "Max businesses",
            "minimum": 1,
            "maximum": 10000,
            "type": "integer",
            "description": "Cap the whole run — you are charged per delivered business, so this is your cost ceiling. Anything from <b>1 to 10,000</b> is accepted. With several categories or cities the budget is <b>shared</b> between the searches, not multiplied by them. Businesses removed by a filter never count against it and are never billed. The Console form starts at 25 (about $0.075 of leads at $3 per 1,000); raise it once the first run looks right. API calls, tasks and schedules that send no <code>maxItems</code> keep the default of 100. Large orders: the run tells you and reduces the order if it cannot crawl and enrich that many in the time available, so you are not billed for rows the run had no time to enrich. Rows with no email are delivered and billed by default - only <code>onlyWithEmail</code> guarantees an email on every billed row. Depth is limited by what OpenStreetMap holds - a single category in a single city is often a few hundred businesses, so five figures needs several categories or cities.",
            "default": 100
          },
          "expandNearby": {
            "title": "Widen to nearby areas when the city runs out (recommended)",
            "type": "boolean",
            "description": "A single city often holds fewer businesses than Max businesses asks for — measured live: 'dentist' in Austin tops out near 119. With this on, the run automatically widens the same search in growing rings around the city (up to ~150 km) until your cap is met or the region is genuinely exhausted. Every widened row is labelled in <code>query_location</code> ('within ~40 km of Austin, Texas'), so you can always tell them apart or filter them out. Off = strict city limits, exactly the previous behaviour. Ignored when you set your own radius (searchRadiusKm) or supply your own website list.",
            "default": false
          },
          "onlyWithWebsite": {
            "title": "Website required (recommended)",
            "type": "boolean",
            "description": "Keep only businesses that list a website — this cuts your bill, because filtered businesses are dropped BEFORE you are charged. Strongly recommended for lead generation: OpenStreetMap businesses without a website almost never carry a phone, email or social profile either — on the reference run all 29 such businesses had none of the three, so they were name + coordinates + address only, and all graded F. The default is off so density research and existing API callers still get every mapped location; the Console form switches it on for you.",
            "default": false
          },
          "onlyWithoutWebsite": {
            "title": "Businesses with no website",
            "type": "boolean",
            "description": "Keep only businesses that do NOT list a website — the prospect list for web-design and digital agencies pitching a first website. Mutually exclusive with <b>Website required</b>: setting both stops the run straight away with a message naming the clash, delivers nothing and charges nothing. Filtered businesses are dropped BEFORE billing. Note these businesses are name + address + coordinates (and occasionally a phone) only: no email, platform or tech enrichment is possible without a website to crawl.",
            "default": false
          },
          "onlyWithEmail": {
            "title": "Email required",
            "type": "boolean",
            "description": "Keep only businesses where an email was found — the cheapest way to buy emails specifically, since businesses without one are dropped before billing and you pay only for contactable leads. With this on, the Actor over-fetches and keeps crawling extra candidates until it has <code>maxItems</code> businesses with an email or the area is exhausted, so asking for 100 emails delivers as close to 100 as the city allows rather than ~40. The candidate pool is 6x <b>Max businesses</b> for a single filter and deepens as you stack filters, up to 24x, and never past 30,000 candidates in one run — that bounds the crawling, not your bill: only delivered rows are charged. A guessed address (see <b>Guessed info@ address</b>) never satisfies this filter.",
            "default": false
          },
          "verifyEmails": {
            "title": "Email verification",
            "type": "boolean",
            "description": "Check every email's domain for real mail servers (MX) and flag role (<code>info@</code>, <code>sales@</code>) and disposable addresses — included at no extra charge, no key needed, and it protects your sender reputation. With this ON, <code>email_status</code> is <code>deliverable</code>, <code>risky</code> or <code>undeliverable</code> when an email was found and <code>missing</code> when none was; with it OFF the values are <code>found</code> and <code>missing</code> instead, so turning verification off changes the vocabulary. Also populates <code>email_provider</code> (Google Workspace, Microsoft 365...). It is a syntax + MX + role + disposable check over DNS, never an SMTP mailbox probe, so an address on a catch-all domain or a mailbox that was deleted can still grade <code>deliverable</code>.",
            "default": true
          },
          "onlyVerifiedEmail": {
            "title": "Verified email required",
            "type": "boolean",
            "description": "Drop leads whose email fails deliverability verification, keeping only <code>email_status</code> <code>deliverable</code> or <code>risky</code> (a domain with real mail servers); businesses with no email at all are dropped too. Requires <b>Email verification</b> to be on — set it without verification and the run turns verification on for you rather than delivering rows the filter is meant to remove. Like <b>Email required</b>, this over-fetches (a pool of 6x <b>Max businesses</b>, deepening to 24x as you stack filters, capped at 30,000 candidates) until <code>maxItems</code> verified leads exist or the area is exhausted.",
            "default": false
          },
          "requirePhone": {
            "title": "Phone number required",
            "type": "boolean",
            "description": "Drop every business with no phone number, before billing. The phone can come from OpenStreetMap or from the business's own website (a <code>tel:</code> link or its schema.org markup) — if <b>Phone numbers from websites</b> is off, only map-sourced numbers count. Combine with <b>Email required</b> for a fully contactable list.",
            "default": false
          },
          "requireSocial": {
            "title": "Social profile required",
            "type": "boolean",
            "description": "Drop every business with no Facebook, Instagram, LinkedIn, X/Twitter or YouTube profile, before billing — useful for social-media agencies and DM-first outreach. Needs website crawling (or an OpenStreetMap social tag) to find anything: with crawling off, almost every business is dropped.",
            "default": false
          },
          "requireAnyContact": {
            "title": "At least one way to contact them",
            "type": "boolean",
            "description": "Drop every business that has <b>no phone, no email and no social profile</b>, before billing. This is the loosest possible contactability floor — a business only needs ONE reachable channel to survive it, unlike <b>Phone number required</b> or <b>Email required</b>, which each demand a specific one.<br><br>Worth knowing: OpenStreetMap maps plenty of businesses as little more than a name and a map pin. On an unfiltered run of the default search roughly half the delivered rows carried no contact details of any kind, and you were billed for them. This switch removes exactly those rows. Left off by default so that nothing about an existing run, task or API call changes.",
            "default": false
          },
          "excludeKeywords": {
            "title": "Excluded name keywords",
            "type": "array",
            "description": "List the words that disqualify a business by NAME — add <code>Aspen Dental</code> to strip a chain out of a dentist list. Case-insensitive and matched anywhere in the name; only the name is matched — never the address, website or email — so <code>clinic</code> cannot knock out a business on Clinic Street. Typical uses: filter out chains and franchises, or your own existing customers. Businesses are dropped before crawling and before billing. The form arrives with this list empty, so nothing is excluded until you add a keyword; API callers who send nothing get no exclusions at all. Whenever it removes anything, the run logs how many businesses your exclusion list took out in total (one figure for the whole list, not one per keyword).",
            "items": {
              "type": "string"
            }
          },
          "excludeChains": {
            "title": "Independents only (no chains)",
            "type": "boolean",
            "description": "Drop every business OpenStreetMap marks as a chain outlet — a <code>brand</code> or <code>brand:wikidata</code> tag, which is what sets the <code>is_chain</code> column to <code>true</code> — before crawling and before billing, leaving the independents. Decided from the map data alone, so no crawl budget is spent on a dropped outlet, and the run says how many it removed. Businesses you supply yourself (<b>websiteList</b> / <b>startUrls</b>) have no brand tag (<code>is_chain</code> is null) and are never dropped by this. Off by default; on a schedule with <b>Only new businesses</b> it joins the watch's memory key only when you switch it on, so existing schedules keep their memory.",
            "default": false
          },
          "skipClosed": {
            "title": "Skip closed businesses",
            "type": "boolean",
            "description": "Drop OpenStreetMap elements the mappers have retired: a lifecycle-prefixed tag (<code>disused:amenity</code>, <code>abandoned:shop</code>, <code>was:*</code>), a <code>disused=yes</code> / <code>abandoned=yes</code> flag, <code>opening_hours=closed</code>, or <code>shop=vacant</code>. Measured live on 2026-08-08: 199 such elements in the Austin bounding box, 43 of them still carrying a business name. They cannot reach a normal tagged search, but they DO reach the business-name fallback used for unmapped categories — which is where dead businesses were being delivered as fresh leads. Off by default so existing runs are unchanged; the Console form switches it on for you.",
            "default": false
          },
          "minScore": {
            "title": "Minimum lead score",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "Keep only leads scoring at or above this value, 0-100, where <code>0</code> means no filter. Every lead is scored from its verified email (deliverable 40 / risky 25 / unverified 15 / verified-undeliverable 0, halved if the address belongs to a third-party marketing agency), phone 20, website 15, socials 5 each capped 10, and extra signals 5 each capped 15 (website platform detected, opening hours, marketing tech, star rating — <code>score_breakdown.signals_from</code> names the ones that counted). Grades: A&gt;=80, B&gt;=65, C&gt;=50, D&gt;=35, else F. This is a <b>data-completeness</b> score, NOT a customer rating.",
            "default": 0
          },
          "maxScore": {
            "title": "Maximum lead score",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "Keep only leads scoring at or BELOW this value, 0-100 — the web-design agency filter. A low score means a thin online presence (no email published, no marketing tech, often a builder-tier website), which is exactly the redesign pitch list. Try <code>45</code> to get the neglected half. Accepts 0-100; leave empty for no upper limit. Setting a maximum below the minimum stops the run straight away with a message and charges nothing."
          },
          "minRating": {
            "title": "Minimum star rating",
            "minimum": 1,
            "maximum": 5,
            "type": "number",
            "description": "Keep only businesses with a published star rating at or above this value, 1-5. <b>READ THIS BEFORE USING IT:</b> OpenStreetMap carries NO review data. A rating only exists when the business publishes schema.org <code>aggregateRating</code> on its own website, measured at roughly 3-8% of businesses. Businesses with NO rating are DROPPED, not kept — an unknown rating is not a passing rating, and no rating is ever invented to save a business. Setting this will therefore slash the number of businesses you get to a small fraction. If you need ratings on every business, use a Google Maps source instead; every business here carries a <code>google_maps_url</code> to the live listing."
          },
          "minReviewCount": {
            "title": "Minimum review count",
            "minimum": 1,
            "maximum": 100000,
            "type": "integer",
            "description": "Keep only businesses whose own website publishes a review count at or above this value (1-100,000). Same warning as the minimum star rating: review counts come only from schema.org markup on the business's own website (roughly 3-8% of businesses), never from OpenStreetMap, and businesses with no review count are DROPPED rather than kept. Expect very few businesses to survive it."
          },
          "onlyNewBusinesses": {
            "title": "Only new businesses since the last run",
            "type": "boolean",
            "description": "Off by default. The FIRST run of a watch is the baseline — it delivers everything it finds and says so. Every later run with the <b>same categories, locations and filters</b> delivers ONLY businesses that were not delivered before; already-delivered ones are dropped <b>before the crawl</b>, so they cost nothing and are never billed. A run where nothing is new delivers 0 rows, bills nothing, and says “Nothing new … You were not charged.”<br><br>A business is remembered by its OpenStreetMap object identity (<code>node/123456789</code>), so a rename or a new domain does not make it look new again; websiteList rows are remembered by their domain. A row is marked seen only <b>after</b> it has actually been delivered, so nothing you were not sent can be skipped next time.<br><br>The watch is identified by your search + the filters that shape it (categories, locations, countryCode, keyword, radius/centre, expandNearby, websiteList, and every <b>Filters</b> option). Change any of them and you start a separate watch with its own memory — <b>maxItems</b>, <b>sortBy</b> and <b>outputFields</b> are deliberately NOT part of it, so tuning them never re-bills you for businesses you already have. Memory lives in a named key-value store (<code>local-business-leads-monitor</code>) in your own account, is kept for 90 days, and holds the 50,000 most recent businesses per watch — past that the oldest are forgotten and could be delivered (and billed) again.<br><br>Note: with this on, the run searches a deeper pool of candidates (the already-delivered ones have to be skipped over), which takes longer than the same run without it.",
            "default": false
          },
          "crawlEmails": {
            "title": "Website crawl",
            "type": "boolean",
            "description": "Visit each business's own website to extract a contact email, 5 social profiles, phone numbers, the website platform (WordPress, Wix, Squarespace, Shopify, GoDaddy...), marketing/booking tech, and any star rating it publishes. Turn it off and every website-derived column stays empty and the email filters become unusable. Included in the per-business price.<br><br>Every row records what happened to its website in <code>website_platform_status</code>: <code>detected</code> (platform identified), <code>unknown</code> (a page loaded, nothing recognisable on it), <code>site_blocked</code> (the site answered but refused us — a 403, a bot-check page or a login wall), <code>not_found</code> (every URL that answered came back 404 or 410 — the address in the listing is dead, the host serving it is not), <code>site_error</code> (the site answered with a server error), <code>no_page</code> (it answered, but never with a readable web page), <code>site_unreachable</code> (nothing answered at all — DNS failure, refused connection, broken TLS or a timeout), <code>no_website</code> (there was no website to crawl), <code>not_crawled</code> (crawling was off) and <code>crawl_error</code> (our own crawler failed on that site — worth reporting). Only <code>site_unreachable</code> means the business has no reachable website, and only <code>crawl_error</code> says nothing at all about the site (it is a fault on our side); every other value tells you the website exists and why we could not read it, so a blocked or 404 site is still a live prospect.",
            "default": true
          },
          "crawlSocialProfiles": {
            "title": "Social profiles",
            "type": "boolean",
            "description": "Fill the <code>facebook</code>, <code>instagram</code>, <code>linkedin</code>, <code>twitter</code> and <code>youtube</code> columns from the crawled pages, with share buttons, tracking pixels and post embeds filtered out. On by default — that is the current behaviour. Turn it off to leave the five social columns empty and keep exports narrower. Social links that OpenStreetMap itself carries are unaffected.",
            "default": true
          },
          "extractPhonesFromSite": {
            "title": "Phone numbers from websites",
            "type": "boolean",
            "description": "Collect every <code>tel:</code> number on the crawled pages into the <code>phones</code> column, and use one to fill <code>phone</code> when OpenStreetMap has none. On by default — that is the current behaviour. Turn it off if you only trust map-sourced numbers: <code>phone</code> then means \"what OpenStreetMap said\" and nothing else, and <code>phones</code> stays empty.",
            "default": true
          },
          "emailPatternGuess": {
            "title": "Guessed info@ address",
            "type": "boolean",
            "description": "Write a GUESSED <code>info@&lt;domain&gt;</code> into the separate <code>email_guess</code> column when a business has a website but publishes no address anywhere that was crawled — OFF by default, and it stays out of your <code>email</code> column on purpose. <code>email_guess_confidence</code> is <code>mx_ok</code> (the domain does run mail servers), <code>no_mx</code> (it does not — the guess is almost certainly dead) or <code>unchecked</code> (email verification is off). A guess is never promoted to <code>email</code>, never counts as <code>has_email</code>, never earns a lead-score point and never satisfies the email filters. Treat it as a lead, not a verified mailbox — nobody checked that this mailbox exists.",
            "default": false
          },
          "maxPagesPerSite": {
            "title": "Max pages per website",
            "minimum": 1,
            "maximum": 8,
            "type": "integer",
            "description": "Set how deep to look for an email on each website, 1-8 pages (default 3): fixed contact paths, plus up to 4 contact/about/team links discovered in the website's own menu (covers Shopify <code>/pages/contact</code> and non-English slugs), plus a bare-domain fallback. Higher finds more emails and takes longer; it does not change what you are charged per business.",
            "default": 3
          },
          "concurrency": {
            "title": "Concurrency",
            "minimum": 1,
            "maximum": 50,
            "type": "integer",
            "description": "Set how many business websites to crawl in parallel, 1-50 (default 8). Raise it to finish a large run faster - the run's own time estimate scales with it, so a 10,000-business order needs it well above the default to fit one run timeout - and lower it if websites start refusing connections. Requests to any one website stay capped at 2 in flight whatever you set here, and OpenStreetMap lookups are not affected by it.",
            "default": 8
          },
          "searchRadiusKm": {
            "title": "Search radius",
            "minimum": 1,
            "maximum": 100,
            "type": "number",
            "description": "Search a circle around a point instead of a city's bounding box — <code>5</code> covers every business within 5 km of one address. Accepts 1-100 km; for anything wider, search by city with <b>Widen to nearby areas</b>, which rings out to ~150 km. Set this together with the latitude and longitude below. When set, it REPLACES the location/locations bounding box entirely (no geocoding happens, so the <code>country</code> column stays empty) and <b>Widen to nearby areas</b> is ignored, because you defined the area yourself. Leave empty to search by city."
          },
          "centerLat": {
            "title": "Centre latitude",
            "minimum": -90,
            "maximum": 90,
            "type": "number",
            "description": "Enter the latitude of the radius centre, -90 to 90, decimals allowed — <code>30.2672</code> is downtown Austin. Required when a search radius is set, ignored otherwise."
          },
          "centerLon": {
            "title": "Centre longitude",
            "minimum": -180,
            "maximum": 180,
            "type": "number",
            "description": "Enter the longitude of the radius centre, -180 to 180, decimals allowed — <code>-97.7431</code> is downtown Austin. Required when a search radius is set, ignored otherwise."
          },
          "websiteList": {
            "title": "Your own website list",
            "type": "array",
            "description": "Paste websites or bare domains you already have — <code>example.com</code>, <code>https://www.example.com/contact</code> — and business discovery is SKIPPED entirely: only the crawl + email verification + scoring pipeline runs over your list. Use it to enrich a CRM export, a conference exhibitor list or a competitor's client list. These businesses carry <code>source='user_supplied'</code>, an empty attribution (they are not OpenStreetMap data, so no ODbL notice is attached), no coordinates and no OSM ids; their name comes from the website's own page title, falling back to the bare domain. Filters and scoring work exactly as on discovered businesses. Entries pointing at the same domain (<code>example.com</code>, <code>https://www.example.com</code>, <code>example.com/contact</code>) are merged, so one business is crawled, delivered and billed once. <b>Max businesses</b> caps how much of the list is used: with no row-dropping filter set, only the first <code>maxItems</code> websites are crawled — raise it to cover a longer list — and whenever the cap trims your list the run logs how many entries it actually used. If you supply a list and NONE of the entries is a usable website or domain, the run stops with an error instead of quietly falling back to an OpenStreetMap search you did not ask for — nothing is charged.",
            "items": {
              "type": "string"
            }
          },
          "startUrls": {
            "title": "Start URLs",
            "type": "array",
            "description": "Use this instead of <b>Your own website list</b> when your integration already speaks Apify's usual <code>startUrls</code> convention — it behaves identically. Both lists are merged and deduplicated by registered domain (not by URL), so <code>example.com</code> and <code>https://www.example.com/contact</code> are one business — crawled, delivered and billed once.",
            "items": {
              "type": "string"
            }
          },
          "outputFields": {
            "title": "Output columns",
            "type": "array",
            "description": "List the columns you want in the dataset — <code>name</code>, <code>email</code>, <code>phone</code>, <code>website</code>, <code>lead_grade</code>. Leave empty to get all 73. Every delivered business carries exactly the same columns in the documented order, so CSV headers never shift mid-export. <code>name</code> and <code>attribution</code> are always included whatever you choose — attribution because the OpenStreetMap licence (ODbL) has to travel with the data into your CSV. Names are matched case-insensitively and <code>-</code>/space count as <code>_</code>, so <code>Lead Grade</code> and <code>email-status</code> resolve. An unknown name is reported in the log and ignored; but if NONE of the names you list exists the run stops before billing, because a name-only export at full price is not worth paying for.<br><br>Valid names: name, category, category_match, address, city, state, postal_code, country, latitude, longitude, query_category, query_location, phone, phones, website, domain, email, emails, email_status, email_provider, email_domain_match, email_type, email_types, contact_page_url, email_guess, email_guess_confidence, facebook, instagram, linkedin, twitter, youtube, website_platform, website_platform_status, platform_version, site_generator, tech, has_meta_pixel, has_google_analytics, has_booking_widget, website_title, website_description, mobile_viewport, copyright_year_stale, opening_hours, rating, review_count, price_range, lead_score, lead_grade, lead_tier, score_breakdown, has_email, has_phone, has_website, enriched_from_website, source, attribution, google_maps_url, osm_url, osm_type, osm_id, has_google_ads_tag, speciality, cuisine, wheelchair, operator, osm_description, brand, brand_wikidata, is_chain, city_source, osm_last_edited, osm_check_date.",
            "items": {
              "type": "string"
            }
          },
          "sortBy": {
            "title": "Sort order",
            "enum": [
              "score_desc",
              "name_asc",
              "review_count_desc",
              "rating_desc"
            ],
            "type": "string",
            "description": "Choose the order the businesses arrive in — <b>Lead score, best first</b> is the default and what previous builds always did. Sorting never removes a business; all filtering happened earlier. Businesses missing the sort value (no rating, no review count) are placed last, because a missing value is unknown rather than zero.",
            "default": "score_desc"
          },
          "webhookUrl": {
            "title": "Alert webhook URL (Slack, Discord, n8n, Make, Zapier…)",
            "type": "string",
            "description": "Optional. When at least one row is delivered, the run POSTs a digest to this URL: a Slack incoming webhook gets a text message, a Discord webhook gets a message, any other URL (n8n / Make / Zapier catch hook, your own endpoint) gets JSON with the counts, console links and the first 20 rows. Quiet runs send nothing. Pair it with onlyNewBusinesses on a schedule and this actor becomes an alert service on its own. Delivery is best-effort: a webhook failure is reported in the status message and never fails the run."
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}