# Contact Page & Form Finder: Contact Form URL, Captcha (`datagleaner/contact-page-finder`) Actor

Find the contact page and contact form of any list of websites: contact page URL, form kind (HubSpot, Typeform, Google Forms, Jotform, WordPress plugins or plain HTML), form fields, captcha present, mailto present. Never submits forms; no emails returned. $1 per 1,000 sites with a contact page.

- **URL**: https://apify.com/datagleaner/contact-page-finder.md
- **Developed by:** [Data Gleaner](https://apify.com/datagleaner) (community)
- **Categories:** Lead generation, Automation, Integrations
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 contact page founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Contact Page & Form Finder: Contact Form URL, Form Kind, Captcha

**Give it a list of websites; get back each site's contact page URL, what kind of contact form it uses (HubSpot, Typeform, Google Forms, Jotform, WordPress form plugins or a plain HTML form), whether a captcha guards it, and whether the site shows a mailto link. $1 per 1,000 sites where a contact page is found; everything else is free.**

It reads pages only. It never submits a form, and it never returns an email address or a phone number.

### What it does

For each website it reads the home page, follows the links that name a contact page ("Contact", "Contact us", "Get in touch", "Kontakt", "Contacto", "お問い合わせ" and about 40 more), and if none are linked it tries a few common paths such as `/contact`. On the contact page it reports:

- `contactPageUrl`: the contact page (or the home page, when its own form is the only contact form)
- `formFound`, `formKind`, `formKinds`: whether there is a contact form and which tool renders it: `hubspot`, `typeform`, `google_forms`, `jotform`, `microsoft_forms`, `tally`, `formspree`, `wufoo`, `formstack`, `cognito_forms`, `zoho_forms`, `marketo`, `pardot`, `contact_form_7`, `wpforms`, `gravity_forms`, `ninja_forms`, `elementor`, `webflow`, `squarespace`, `wix`, or `html` for a site-built form
- `form`: for a site-built form, its action URL, method and visible fields (name, type, required), so you can map it in your own tooling
- `captchaPresent`, `captchaKinds`: `recaptcha`, `hcaptcha`, `turnstile`, `friendly_captcha` or `other`
- `mailtoPresent`, `telLinkPresent`: whether the contact or home page has a mailto: or tel: link (true/false only)
- `otherContactPages`: other contact pages found on the site (sales, support)
- `status` and `failureReason`: `found`, `no_contact_page`, `unreachable`, `blocked`, `robots_blocked`, `invalid_input` or `opted_out`, with the reason

Search, login and newsletter-only forms are not counted as contact forms.

### How it differs from Website Contact Details Scraper

This Actor and our [Company Contact Details & Website Email Finder](https://apify.com/datagleaner/website-contact-details-scraper) (website-contact-details-scraper) read the same kind of sites, but they answer different questions and bill different rows:

| | Contact Page & Form Finder (this Actor) | Website Contact Details Scraper |
|---|---|---|
| Question it answers | Where is the contact page, and what form is on it? | What are the company's emails, phones and social links? |
| Returns emails or phones | No, never (mailto/tel presence only) | Yes |
| Form kind, form fields, captcha | Yes | No |
| Billed rows | Sites with a contact page found, $0.001 | Sites with an email found, $0.002 |
| Pages per site | 2 on most sites (4 at most by default) | Up to 10 |

If you need the email addresses themselves, use [Website Contact Details Scraper](https://apify.com/datagleaner/website-contact-details-scraper). If you need to know where and how a site takes contact requests, use this one.

### Use cases

- **Form-based outreach lists:** sort a lead list into sites with a plain HTML form, a HubSpot form, a Typeform, or only a mailto link, before you choose a channel.
- **Agency and CRO audits:** see which prospects still run an unguarded form, or a form with no captcha, or no contact page at all.
- **Martech research:** count how many sites in a segment use HubSpot, Typeform, Jotform or Contact Form 7 for their contact form.
- **Data cleaning:** add a contact page URL column to a CRM export so reps land on the right page.

### Input example

```json
{
  "websites": ["stripe.com", "posthog.com", "https://mattermost.com", "www.jotform.com"],
  "maxPagesPerSite": 4,
  "maxConcurrency": 10
}
```

Plain domains, `www.` hosts and full URLs all work. Duplicate domains are read once. An empty input runs a 2-site example and says so in the run's status message.

### Output example

```json
{
  "input": "www.jotform.com",
  "website": "https://www.jotform.com/",
  "domain": "jotform.com",
  "status": "found",
  "billed": true,
  "contactPageUrl": "https://www.jotform.com/contact/",
  "contactPageSource": "link",
  "contactPageTitle": "Contact Us | Jotform",
  "otherContactPages": [],
  "formFound": true,
  "formKind": "jotform",
  "formKinds": ["jotform", "html"],
  "form": {"action": "https://www.jotform.com/contact/", "method": "post", "fields": [{"name": "name", "type": "text", "required": true}]},
  "captchaPresent": true,
  "captchaKinds": ["recaptcha"],
  "mailtoPresent": false,
  "telLinkPresent": false,
  "pagesFetched": 2,
  "robotsBlocked": 0,
  "failureReason": null,
  "scrapedAt": "2026-11-02T03:20:00Z"
}
```

### Pricing

Pay per event: **$0.001 per website where a contact page is found** (`contact-page-found`), that is $1 per 1,000 found. Sites with no contact page, unreachable, blocked, robots-blocked and invalid sites are returned free. These prices are final.

Worked example: 1,000 websites, of which 780 have a contact page, 150 have none and 70 are unreachable, cost 780 x $0.001 = **$0.78**. Set "Maximum cost per run" in the run options to cap any run; the Actor stops cleanly when the cap is reached.

### Limits

- Plain HTTP, no browser. A form that a page builds entirely in JavaScript after load can be missed (the page is still reported as the contact page when a link names it). Captchas loaded inside a hosted form's own iframe (inside a Typeform, for example) are not visible from the host page.
- A site that answers 403, 429, 503 or a bot challenge is reported as `blocked`. It is retried once the same way after a pause, never through another IP, header set or browser.
- robots.txt is binding: disallowed pages are not fetched (`robotsBlocked` counts them) and Crawl-delay is honoured. A site whose robots.txt disallows its home page is `robots_blocked`.
- Each site gets one request at a time and at most 45 seconds.

### FAQ

**Does it fill in or submit forms?** No. It only makes GET requests to read pages. It never posts to a form.

**Why no emails?** This Actor maps contact channels, not people. `mailtoPresent` tells you a mailto link exists; use Website Contact Details Scraper if you need the address.

**What counts as a contact page?** A same-site page that a link names as a contact page, or a common contact path that loads and shows a form or a contact title. Pages on a subdomain count only when the link clearly names a contact page.

**Can I feed it Google Maps or CRM results?** Paste the websites into `websites`, or call it from an integration with your list.

### Responsible use

This Actor reads publicly available pages, respects each site's robots.txt and identifies itself with an honest user agent (KeencrestBot). It returns no personal data: no names, emails or phone numbers. If you use the results to send messages through websites' contact forms, you are responsible for following the law that applies to you and to the recipient (anti-spam and e-marketing rules) and each site's own terms; many sites forbid unsolicited commercial messages through their forms.

### Removal requests

A site owner who wants their site left out of this Actor's results can write to datagleaner@gmail.com with the domain; it is added to our shared removal list, and later runs return that site as `opted_out` without reading it.

Built and run by Keencrest (Data Gleaner on Apify). Crest, Keencrest's AI agent, maintains this Actor; the business is owner-operated.

# Actor input Schema

## `websites` (type: `array`):

Company domains or website URLs, one per line. `stripe.com`, `www.stripe.com` and `https://stripe.com/` all work. Each site is read from its home page. Leave empty to run a 2-site example.

## `maxPagesPerSite` (type: `integer`):

Pages read per site, home page included: the home page, then the links that name a contact page, then a few common paths such as /contact. Most sites need 2.

## `maxConcurrency` (type: `integer`):

How many sites are read at the same time. Each site gets one request at a time.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one page.

## `proxyConfiguration` (type: `object`):

Off by default. Your own egress choice: one session is kept for the whole run. A site that blocks or challenges the request is reported as blocked, never retried through another IP.

## Actor input object example

```json
{
  "websites": [
    "stripe.com",
    "posthog.com",
    "mattermost.com",
    "plausible.io",
    "jotform.com"
  ],
  "maxPagesPerSite": 4,
  "maxConcurrency": 10,
  "requestTimeoutSecs": 15
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "websites": [
        "stripe.com",
        "posthog.com",
        "mattermost.com",
        "plausible.io",
        "jotform.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagleaner/contact-page-finder").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "websites": [
        "stripe.com",
        "posthog.com",
        "mattermost.com",
        "plausible.io",
        "jotform.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("datagleaner/contact-page-finder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "websites": [
    "stripe.com",
    "posthog.com",
    "mattermost.com",
    "plausible.io",
    "jotform.com"
  ]
}' |
apify call datagleaner/contact-page-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datagleaner/contact-page-finder"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/13EzmBn7HpjRMA2pX/builds/NTFmOXOp3o6o2F3FK/openapi.json
