{
  "openapi": "3.0.1",
  "info": {
    "title": "Website Email Extractor for Google Maps Leads & Domain Lists",
    "description": "Website email extractor and contact details scraper: paste domain lists, company names, PDF links or a Google Maps dataset; get best email (MX checked), phones and social links per site. $2 per 1,000 websites with a contact found; nothing found = free. Does not scrape Maps.",
    "version": "0.1",
    "x-build-id": "qtaNSeiQRmn6nSSco"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/tidytools~website-contact-extractor/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-tidytools-website-contact-extractor",
        "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/tidytools~website-contact-extractor/runs": {
      "post": {
        "operationId": "runs-sync-tidytools-website-contact-extractor",
        "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/tidytools~website-contact-extractor/run-sync": {
      "post": {
        "operationId": "run-sync-tidytools-website-contact-extractor",
        "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": {
          "domains": {
            "title": "Websites or company names",
            "type": "array",
            "description": "One website per line: acme.com, www.acme.com or a full URL; a spreadsheet column works. A company name works too with its city, address or phone (Joe's Pizza, New York). A path (acme.com/contact) is an extra page. A PDF link (acme.org/members.pdf) is read as a document: one row per PDF, charged like a website. Each website is one row, charged once (www variants and duplicates count once); blank, invalid and unverified lines are free. Up to 50,000 per run. Or use startUrls or datasetId.",
            "items": {
              "type": "string"
            }
          },
          "startUrls": {
            "title": "Website URLs (optional)",
            "type": "array",
            "description": "The same as \"Websites or company names\", as a URL list; you can also upload or link a text/CSV file of websites. Bare domains belong in the list above (this editor rejects them).",
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL of a web page",
                  "format": "uri"
                }
              }
            }
          },
          "findWebsitesForNames": {
            "title": "Find websites for company names",
            "type": "boolean",
            "description": "Boolean, true by default. With {\"findWebsitesForNames\":true,\"domains\":[\"Joe's Pizza, 7 Carmine St, New York, NY\"]}, a company-name line is looked up as a website. Put its city, address or phone on the same line, or paste tab/comma-separated columns with a Name,City,Phone header; names alone are ambiguous. Only verified websites are used. No verified website gives a free company_not_found row; false makes name lines free invalid rows.",
            "default": true
          },
          "namesCountry": {
            "title": "Country of the company names (optional)",
            "type": "string",
            "description": "2-letter country code (e.g. DE, GB, JP) so that company names are first tried on that country's domains (.de, .co.uk, .co.jp) before .com. Empty = .com, .io, .co."
          },
          "datasetId": {
            "title": "Google Maps / other source dataset ID (optional)",
            "type": "string",
            "description": "First run a Google Maps scraper (e.g. compass/crawler-google-places) or another directory scraper, then select that run's dataset here. This Actor reads websites listed in the dataset; it does not scrape Google Maps. For Maps results, it finds the website field automatically and keeps each place's name, address, phone, Maps link and placeId (placeName, placeAddress, placePhone, mapsUrl, placeId) so emails merge back into your export."
          },
          "datasetField": {
            "title": "Dataset field",
            "type": "string",
            "description": "Field (or \"rows\" column) with the website or domain, e.g. \"website\" (dot paths such as \"company.url\" work). Empty = found automatically: website, then url, domain, link... A field that only holds Google Maps links (the \"url\" of a Maps place) is skipped, so Google Maps results need no setting."
          },
          "datasetKeepFields": {
            "title": "Dataset fields to keep",
            "type": "array",
            "description": "Extra fields copied from each dataset item or rows entry into the output's \"source\" object, e.g. Business, Website, row_number, Email. For CRM review CSV, retain the source columns needed to identify each company. Google Maps results already get flat place columns (placeName, placeAddress, placePhone, mapsUrl, placeId, placeCategory, placeCity, placeCountryCode) without this setting.",
            "items": {
              "type": "string"
            }
          },
          "rows": {
            "title": "Rows from a spreadsheet or CRM (API / n8n / Make)",
            "type": "array",
            "description": "Instead of a dataset: the rows of your sheet as JSON objects, e.g. {\"rows\":[{\"row_number\":1,\"Business\":\"Acme\",\"Website\":\"a.example\",\"Email\":\"\"}],\"rowIdField\":\"row_number\",\"datasetKeepFields\":[\"row_number\",\"Business\",\"Website\",\"Email\"],\"crmReviewExport\":true}. Replace a.example with your site. Every source row keeps its identifier and its own Dataset output row."
          },
          "mapsData": {
            "title": "Pasted Google Maps JSON or CSV (optional)",
            "type": "string",
            "description": "Alternative to rows or datasetId: paste a Maps JSON array, an object with items, or CSV with a website column. A datasetId takes precedence over sample pasted data."
          },
          "mapsPlaceRows": {
            "title": "Google Maps place view (optional)",
            "type": "boolean",
            "description": "Return CRM-ready place columns and preserve each original Maps item in source. Default off leaves this Actor's existing output unchanged.",
            "default": false
          },
          "lookupMapsWithoutWebsite": {
            "title": "Find websites for Maps places without one (experimental)",
            "type": "boolean",
            "description": "For Maps source rows with no website, look up the place name with its city, address and phone. Only verified company websites are read. Off by default until a 30-place precision check is complete; unmatched rows stay free.",
            "default": false
          },
          "rowIdField": {
            "title": "Row identifier field",
            "type": "string",
            "description": "Field that identifies each row of the dataset or \"rows\" (returned as rowId). Empty = the first of rowId, row_number, placeId, id; otherwise the row position. Branches of a chain that share one website are read once but keep their own rowId."
          },
          "crmReviewExport": {
            "title": "CRM deduplication and conflict review CSV (optional, free)",
            "type": "boolean",
            "description": "After the run, save CRM_REVIEW.csv in this run's key-value store and its download URL in SUMMARY.crmReviewCsvUrl. Only this run's saved Dataset rows are used. Same company and domain may merge in this separate CSV; conflicting or missing company, source or row ID goes to review. Inspect review rows before importing into a CRM. The original Dataset rows and site charges do not change.",
            "default": false
          },
          "targetPersonNameField": {
            "title": "Person name column (optional)",
            "type": "string",
            "description": "Column in the source dataset or rows containing a person's full name, e.g. contactName. Empty = find personName, contactName or fullName automatically; a company Name column is never treated as a person. Marks a unique text pattern in an email already published on the website; it does not guess an address or verify who owns it."
          },
          "emailField": {
            "title": "Email column",
            "type": "string",
            "description": "Field with the email you already have (Email, emails, primaryEmail... are found automatically). Used by \"Only rows without an email\" and for \"fill\": the found email is only offered for rows where this field is empty, so your own values are never overwritten."
          },
          "phoneField": {
            "title": "Phone column",
            "type": "string",
            "description": "Field with the phone you already have (found automatically). The found phone is only put in \"fill\" when it is empty."
          },
          "onlyMissingEmail": {
            "title": "Only rows without an email",
            "type": "boolean",
            "description": "With a dataset or \"rows\": rows that already have an email are not read and not charged (they get one row with emailStatus already_had_email).",
            "default": false
          },
          "requiredContact": {
            "title": "Which results are charged?",
            "enum": [
              "any",
              "email"
            ],
            "type": "string",
            "description": "Choose which results are charged. \"any\" (default): a website with at least one email, phone number or social profile. \"email\": only websites where at least one email address is found are charged; websites with only a phone number or social profile are still returned, but free. The price per website is the same in both modes.",
            "default": "any"
          },
          "roleEmailsOnly": {
            "title": "Role mailboxes only (no personal emails)",
            "type": "boolean",
            "description": "Off (default): every business email published on the website is returned, including addresses that look like a named person (anna.meier@...). On: only shared role mailboxes (info@, contact@, sales@, support@, office@...) are returned and used for billing; personal-looking addresses are dropped before output.",
            "default": false
          },
          "verifyEmails": {
            "title": "Validate found emails (extra charge)",
            "type": "boolean",
            "description": "Optional. Check each found email's syntax, MX and disposable domain using the email validation service. No email is sent and no SMTP probe is made. Each successfully checked email is a separate email-verified event no earlier than 2026-11-07; DNS or service failures are free. Existing syntax and MX quality hints remain free when off.",
            "default": false
          },
          "deepCrawl": {
            "title": "Read additional pages (extra charge)",
            "type": "boolean",
            "description": "Optional. After the normal website crawl, follow more public internal team, staff, leadership, imprint and related pages, even if an email was already found. Each additional successfully read distinct page is a deep-crawl-page event no earlier than 2026-11-07; blocked and failed pages are free.",
            "default": false
          },
          "deepCrawlMaxPages": {
            "title": "Maximum additional pages per website",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "Used only with deepCrawl. At most this many additional successful public pages are read after the regular maxPagesPerSite budget. Default 10.",
            "default": 10
          },
          "socialEnrich": {
            "title": "Social profile source details (free)",
            "type": "boolean",
            "description": "Optional. Return public LinkedIn, X, Facebook, Instagram, YouTube and TikTok profile URLs with the source page. No extra charge: the same profiles are in the free socialProfiles and socialProfileSources fields; this only groups them in one column.",
            "default": false
          },
          "includeLeaders": {
            "title": "Decision makers: names and job titles (extra charge)",
            "type": "boolean",
            "description": "Optional. Also return decision makers the company's own website names (founder, owner, CEO, managing director, VP, director, head, partner): full name, job title, source page, and their email when a published address spells their name. Reads at most 3 extra team / about pages within maxPagesPerSite. 'sites' output: a leaders list per website; 'emailRows': one row per person (rowType: leader). Each person is a leader-row event no earlier than 2026-11-08 (free until then).",
            "default": false
          },
          "maxLeadersPerSite": {
            "title": "Maximum decision makers per website",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "Used only with includeLeaders. The most senior people first (founders, owners and chiefs, then VPs, directors and heads, then managers). Default 5.",
            "default": 5
          },
          "dedupeStoreName": {
            "title": "Skip websites read in earlier runs (dedupe name)",
            "type": "string",
            "description": "Any name, e.g. \"my-lead-list\". Websites (by domain) read in an earlier run with the same name are not read or charged again: the earlier result is returned with fromPreviousRun. Kept in a key-value store \"contacts-<name>\" in your Apify account. Failed websites are not kept, so they are tried again. Empty = off."
          },
          "monitorContactChanges": {
            "title": "Track contact changes across runs",
            "type": "boolean",
            "description": "Optional. Requires a non-empty dedupeStoreName. First run establishes a baseline; later successful rechecks compare email addresses and write CONTACT_CHANGES. For a weekly schedule use dedupeMaxAgeDays: 6 (with 7, a site saved a few seconds later last week still counts as fresh and is only rechecked every other week). A successful recheck is billed under the usual site-processed rule even if no email changed; a cached result is free.",
            "default": false
          },
          "dedupeMaxAgeDays": {
            "title": "Reuse earlier results for (days)",
            "minimum": 0,
            "maximum": 3650,
            "type": "integer",
            "description": "With a dedupe name: results older than this are read again. 0 = no age limit.",
            "default": 90
          },
          "outputMode": {
            "title": "Output format",
            "enum": [
              "sites",
              "emailRows"
            ],
            "type": "string",
            "description": "\"One row per email\" splits each website into one row per email found, with the company name, primary phone, social profiles, register and tax numbers stated on the site (companyIdentifiers), the page where the email was found and whether it is on the website's own domain. The price is the same: each website is charged once (only its first row shows charged: true). A website with a phone or social profile but no email gives one row with email empty.",
            "default": "sites"
          },
          "icp": {
            "title": "Lead score: your ideal customer (optional, free)",
            "type": "object",
            "description": "Free leadScore (0-100) and scoreReasons on every website, to sort who to contact first. Empty: score = contact-data completeness. With a profile, e.g. {\"keywords\": [\"dental\"], \"countries\": [\"US\"], \"preferredRoles\": [\"sales\"], \"requireEmail\": true}, score = 60% fit + 40% completeness; a site missing a required email or phone scores 0. Uses only data already read: no extra pages, no extra charge. See README for all fields."
          },
          "maxPagesPerSite": {
            "title": "Pages per website",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "The home page plus likely contact pages (contact, imprint/Impressum, about, team, support, legal). When those show no email, pages left over go to other pages likely to name a mailbox (policies, catering, jobs, locations...). The price per website is the same however many pages are read. The prefilled example uses 1 (home page only) for a quick first run; the default is 8.",
            "default": 8
          },
          "maxPdfPages": {
            "title": "Pages per PDF",
            "minimum": 1,
            "maximum": 500,
            "type": "integer",
            "description": "For PDF links (a directory, member list or catalog): pages read per PDF, from page 1. Pages with a text layer are read directly. Default 100. A PDF is charged once ($0.002) when it has a contact, however many pages are read.",
            "default": 100
          },
          "maxPdfOcrPages": {
            "title": "Scanned PDF pages read by OCR",
            "minimum": 0,
            "maximum": 20,
            "type": "integer",
            "description": "Scanned pages (images, no text layer) of a PDF read by OCR, at most this many per PDF. Default 3; 0 reads text layers only. On the Apify Free plan at most 3 are read, with free OCR models only; paid plans may set up to 20 and get a more accurate fallback model.",
            "default": 3
          },
          "excludePaths": {
            "title": "Pages not to read (paths)",
            "type": "array",
            "description": "Optional JSON array of site paths beginning with /, e.g. {\"excludePaths\":[\"/blog\",\"/privacy\"]}; enter one per line in the form. /blog excludes /blog/... but not /blogger; matching ignores case and query strings. Home pages and URLs you explicitly give are still read. Empty means no exclusion. A non-path entry causes a free invalid_input row and stops reading.",
            "items": {
              "type": "string"
            }
          },
          "maxConcurrency": {
            "title": "Parallel websites",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "Global maximum websites processed at the same time (each reads up to 2 pages at once). Websites under the same registrable domain are additionally limited to one at a time; other domains can run in parallel.",
            "default": 10
          },
          "siteTimeoutSecs": {
            "title": "Time limit per website (seconds)",
            "minimum": 20,
            "maximum": 900,
            "type": "integer",
            "description": "After this time no further pages of a website are started and the pages read so far are returned (skipped pages are listed in timedOutPages). If even the home page is not read by then, the website fails (not charged).",
            "default": 120
          },
          "httpVia": {
            "title": "Plain HTTP requests from",
            "enum": [
              "auto",
              "backend",
              "direct",
              "vm"
            ],
            "type": "string",
            "description": "Some sites block requests from data centers. Auto retries from a second network before falling back to a real browser. If those routes are refused, Auto also tries our second server (Oracle, different IP) before a real browser.",
            "default": "auto"
          },
          "warpRetry": {
            "title": "Retry refused websites through a second network",
            "type": "boolean",
            "description": "On (default; used with Auto and Our servers only): when a website refuses every request (HTTP 403 or a refusal of automated clients, not a CAPTCHA page), its home page is asked once more through our Cloudflare WARP network with a standard browser User-Agent (still ending with our TidyTools token). A recovered website is a normal row at the normal price; if it refuses again or shows a CAPTCHA or human-verification page, the row stays blocked and free. CAPTCHAs are never solved.",
            "default": true
          },
          "proxyConfiguration": {
            "title": "Proxy (optional)",
            "type": "object",
            "description": "Only used for requests sent from Apify's network. Apify proxy usage is billed to your Apify account."
          },
          "respectRobotsTxt": {
            "title": "Skip pages robots.txt disallows",
            "type": "boolean",
            "description": "Off (default): the websites you list are read even where their robots.txt disallows automated crawlers, as when you open them yourself. Every request still identifies itself as TidyTools; we never log in (sign-in pages are reported as login_required, free), never solve CAPTCHAs and respect Retry-After. On: pages robots.txt disallows for our crawler (TidyToolsContacts) are skipped, and a website whose home page is disallowed is a free robots_disallowed row.",
            "default": false
          }
        }
      },
      "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}