# Contact Form Finder: Website Contact Pages & Forms (`zaiq/contact-form-finder`) Actor

Find the contact page and visible enquiry form on company websites. Return the exact page, form type and evidence. Distinguish contact forms from search, signup and newsletter forms. No emails, personal details or form submissions.

- **URL**: https://apify.com/zaiq/contact-form-finder.md
- **Developed by:** [Zaiq](https://apify.com/zaiq) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 contact pages

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 Form Finder: Website Contact Pages & Forms

Find a website's public contact page and check its HTML for an enquiry form. Each result includes the exact page, a plain verdict, visible field names, the pages checked and an observation time.

**$0.002 per completed contact-page assessment ($2 per 1,000)** on every Apify plan. A confirmed contact page with no observed enquiry form is also charged. Unresolved JavaScript forms, blocks, missing pages and invalid inputs return free notes. There is no custom startup or per-page fee.

Use the ready-to-run Console prefill for a one-site demonstration. The example below is a captured result with the source address replaced; replace example domains in API examples with your own public website URLs.

### What you get

| Field | What it holds |
|---|---|
| verdict | A sentence stating what the checked HTML supports. |
| recordType, resultId | Assessment or free note; a stable ID from the normalized input hostname. |
| website, contactPageUrl, contactPageFound | Input website and confirmed contact-page address. Null means no confirmed page. |
| formState | present, not-observed or unknown-js. Null means assessment did not reach a form decision. |
| formType, formProvider | inline, embedded or unknown; a provider only when an embed reference identifies it. |
| evidence | A generic contact heading label, selected visible labels and field names. No hidden fields, entered values or personal details. |
| checkedPages | Pages assessed, HTTP status when available, outcomes and limitations. |
| coverageComplete | The selected page's static assessment completed. This never means every page or JavaScript form on the website was checked. |
| status, note, sourceUrl, observedAt | Complete, partial, blocked, invalid or no-match; explanation, source and UTC observation time. |

### What people use it for

- **Website routing:** Find the enquiry page for a list of supplied company domains.
- **Contact-form inventory:** Separate static enquiry forms from newsletter, search and account forms.
- **Recurring checks:** Schedule a new run for the same websites and compare page addresses and form states over time.

### How to use it

1. Try the Console prefill, or enter up to 100 public domains or contact-page URLs.
2. Choose up to three pages per site and any additional contact-link languages.
3. Set the application spending limit and Apify's maximum run cost. The lower available limit controls new source work.
4. Open the Contact pages table. Read free notes as well as completed assessments.

### Input

| Field | Example | Notes |
|---|---|---|
| urls | \["https://www.example.org/contact/"] | Bare domains and HTTP(S) page URLs; up to 100 entries. API aliases: url and startUrls (strings or objects with url). |
| maxResults | 10 | 1–100 unique input sites, including sites returning free notes. |
| maxPagesPerSite | 2 | 1–3 HTML pages. Redirects and robots checks share the hard site budget. |
| languages | \["en", "de", "fr", "es"] | English by default. These are link hints, not translation; the page's declared language adds its hints. |
| respectRobotsTxt | true | Always required. False returns a free input note. |
| maxChargeUsd | 0.01 | Application limit in USD; zero makes no website requests. Apify's spending limit also applies. |

### Output

Each result is one row in the run's dataset (download it as JSON, CSV, Excel, XML or HTML, or read it through the API). An example row (values shortened):

```json
{
  "verdict": "An enquiry form is visible in the contact page's HTML.",
  "recordType": "assessment",
  "resultId": "site-example.org",
  "website": "https://www.example.org/contact/",
  "contactPageUrl": "https://www.example.org/contact/",
  "contactPageFound": true,
  "formState": "present",
  "formType": "inline",
  "formProvider": null,
  "evidence": {
    "pageLabel": "contact",
    "visibleLabels": [
      "Name",
      "E-mail",
      "Your message"
    ],
    "fieldNames": [
      "name",
      "email",
      "message_subject",
      "body"
    ],
    "basis": "visible-form-controls"
  },
  "checkedPages": [
    {
      "url": "https://www.example.org/contact/",
      "status": 200,
      "outcome": "checked",
      "formState": "present"
    }
  ],
  "coverageComplete": true,
  "note": "Static HTML assessment of the selected page; scripts, stylesheets and embedded pages were not executed or fetched. This is not an exhaustive website crawl.",
  "status": "complete",
  "sourceUrl": "https://www.example.org/contact/",
  "observedAt": "2026-10-25T12:24:16Z"
}
```

This row comes from a real free local test on 25 October 2026. Site addresses and the domain-based result ID were replaced with example.org; the /contact/ path replaces the source's contact-page path. No real person, account or handle was included. SUMMARY records newly delivered assessments, free notes, duplicate inputs, skipped work, source failures, limits and previously delivered rows skipped after a restart.

### Pricing

- **$0.002 per completed contact-page assessment**, once per normalized input hostname in a run.
- A verified contact page with no observed form is a paid assessment. A page whose form remains unresolved because of JavaScript or an embed is free.
- No custom startup fee, per-request fee, AI charge or residential route. Failure notes and default dataset items have zero event price.
- Every Apify plan pays the same price. Apify adds its standard Actor start charge of $0.00005 a run, shown on the Pricing tab.

What typical jobs cost at the list price:

| Job | Cost |
|---|---|
| 1 completed assessment | $0.002 |
| 10 completed assessments | $0.02 |
| 100 completed assessments | $0.20 |
| 5 completed assessments and 5 failed or uncertain sites | $0.01 |

Both the input limit and the platform limit are checked before fetching a new site. Already delivered assessments count towards the application limit after a restart. Zero-priced notes do not consume an assessment event. To buy 100 assessments, raise maxResults and maxChargeUsd accordingly.

### Tips

- Use an exact contact-page URL when you have one. Homepage discovery is less reliable within a two-page budget.
- A one-page check only assesses the supplied page. A two- or three-page check can follow contact links from it.
- Use a new run to refresh previous results. Within a resumed run, a stable result ID prevents a second copy of an already delivered row.
- Compare sourceUrl and formState across scheduled runs; a no-match or partial result does not prove that no contact form exists.

### Use it through the API

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("zaiq/contact-form-finder").call(run_input={"urls": ["https://www.example.org/contact/"], "maxResults": 1, "maxPagesPerSite": 1, "maxChargeUsd": 0.01})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row)
```

**JavaScript**

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('zaiq/contact-form-finder').call({"urls": ["https://www.example.org/contact/"], "maxResults": 1, "maxPagesPerSite": 1, "maxChargeUsd": 0.01});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

**HTTP (one call, results back)**

```bash
curl -X POST 'https://api.apify.com/v2/acts/zaiq~contact-form-finder/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"urls": ["https://www.example.org/contact/"], "maxResults": 1, "maxPagesPerSite": 1, "maxChargeUsd": 0.01}'
```

### Integrations and schedules

- **Schedules:** run it every hour, day or week from the Apify Console (Schedules) and keep a history of results.
- **Make, Zapier, n8n, Google Sheets, Slack, Airtable:** connect it through Apify's integrations, or send each finished run to a **webhook**.
- **Datasets:** results stay in the run's dataset; export them or read them by API whenever you need them.

### Use it with AI agents (MCP)

Add Apify's MCP server (`https://mcp.apify.com`) to Claude, ChatGPT, Cursor, VS Code or any MCP client. The agent can find this Actor and run it for you: ask it for "find and assess the public contact pages for these website URLs". AI agents can also pay for runs directly through Apify's agentic payments.

### Limits

- Static HTML only: no browser, JavaScript execution, external stylesheet rendering or iframe fetching. A hidden state controlled only by external CSS may not be detectable.
- No email, phone, person or social-profile extraction; no form submissions or outreach.
- Eight seconds, twelve HTTP requests and up to three HTML pages per website. Response bodies are capped at 1 MB decoded and a website at 2 MB decoded. A timeout returns a free note.
- Only the supplied hostname and its www twin are followed. Different subdomains and country domains must be supplied separately. Cross-domain redirects are reported.
- HTTP(S) URLs must use standard web ports and have no login credentials or query parameters. Local/private addresses, login routes and unreadable robots rules are refused.
- The first occurrence of a normalized hostname wins; http/https and www variants are duplicates. Different explicit subdomains are separate inputs.
- Provider names identify observed embed references; they do not prove the embedded form is an enquiry form. Unknown embeds are free.
- The release configuration uses direct requests. One datacenter fallback is implemented but disabled pending cloud validation. There is no residential fallback.

### FAQ

**Does not-observed mean there is no form?**

No. It means no qualifying form was seen in the checked HTML. The Actor does not execute scripts. Evidence of a form that needs scripts produces unknown-js instead.

**Why was a no-form result charged?**

The paid unit is a completed assessment of a confirmed contact page. Unknown JavaScript states, inaccessible pages and unconfirmed page guesses are free.

**Why did a site return no-match?**

No contact page was confirmed within the supplied scope. Try an exact public contact-page URL in a new run.

**Does it fill in or submit forms?**

No. It only sends GET requests for public pages and robots rules.

**Does it solve bot checks or use an account?**

No. Login requirements and bot checks are reported.

**What happens if a run restarts?**

Rows with result IDs already in the dataset are skipped. Do not interpret this as a guarantee of atomic platform billing; a platform interruption between a row write and its charge requires operator review.

### Related Actors from Zaiq

- [contact-details-extractor](https://apify.com/zaiq/contact-details-extractor): when you specifically need public website email or phone extraction

### Support

Questions, a site that changed, or a field you need? Open an issue on this Actor's **Issues** tab: we answer there, usually within a day.

***

Also found as: contact form finder, website contact page finder, contact form detection, contact page API.

# Actor input Schema

## `urls` (type: `array`):

Up to 100 public domains or HTTP(S) URLs. One result per normalized hostname; www and http/https variants are duplicates. The first occurrence wins.

## `maxResults` (type: `integer`):

Maximum unique websites assessed, including sites that return free notes.

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

HTML page budget, including the supplied page. Robots and redirects also count towards a hard 12-request and 8-second website budget.

## `languages` (type: `array`):

Link hints, not translation. English is always recognized; the page's declared language adds its hints automatically.

## `respectRobotsTxt` (type: `boolean`):

Required in this release. False returns a free input note without fetching.

## `maxChargeUsd` (type: `number`):

The lower of this limit and Apify's run spending limit applies. Zero performs no website requests. Already delivered assessments count towards this limit on restart.

## Actor input object example

```json
{
  "urls": [
    "https://www.djangoproject.com/contact/foundation/"
  ],
  "maxResults": 10,
  "maxPagesPerSite": 2,
  "languages": [
    "en"
  ],
  "respectRobotsTxt": true,
  "maxChargeUsd": 0.01
}
```

# Actor output Schema

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

Dataset: the results.

## `summary` (type: `string`):

What each input returned, and notes.

# 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 = {
    "urls": [
        "https://www.djangoproject.com/contact/foundation/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("zaiq/contact-form-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 = { "urls": ["https://www.djangoproject.com/contact/foundation/"] }

# Run the Actor and wait for it to finish
run = client.actor("zaiq/contact-form-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 '{
  "urls": [
    "https://www.djangoproject.com/contact/foundation/"
  ]
}' |
apify call zaiq/contact-form-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zaiq/contact-form-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/5lU0CIDMfnG1wcX3A/builds/NbaAfvZrYJ57czjQK/openapi.json
