Disclosure: I publish the Apify actor used below (pennydata/website-contact-extractor) and earn a fee when it runs. This post was written with AI assistance (Claude). Prices are copied from the public Apify Store API on 2026-10-22 and can change; check the actor pages before you rely on them.
Task: you have a list of company websites and want, for each one, the URL of its contact page and of the page that holds its contact form. website-contact-extractor does that and returns one row per input site.
1. curl (one call, small lists)
curl -s -X POST \ "https://api.apify.com/v2/acts/pennydata~website-contact-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"urls":[{"url":"https://apify.com"},{"url":"https://example.com"}],"maxCostUsd":0.05}' The sync endpoint can time out on big lists; for those use the async run in step 2. maxCostUsd caps what a run can bill.
2. Node.js (apify-client, any list size)
// npm i apify-client import { ApifyClient } from "apify-client"; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const sites = ["https://apify.com", "https://example.com"]; const run = await client .actor("pennydata/website-contact-extractor") .call({ urls: sites.map((url) => ({ url })), maxCostUsd: 0.05 }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); for (const row of items) { console.log(row.input, "| page:", row.contactPageUrl, "| form:", row.contactFormUrl); } Output fields used: input, contactPageUrl, contactFormUrl (the row also has contactFormKind, emails, phones and social links). A site where nothing is found still comes back as a row. Not every site has a findable contact page or a form; expect gaps and check the rows you depend on.
3. Published prices per 1,000 sites (Apify Store API, read 2026-10-22)
Apify pay-per-event prices depend on your Apify plan tier. USD per 1,000 sites charged:
| Actor | Free | Bronze | Silver | Gold+ |
|---|---|---|---|---|
| pennydata/website-contact-extractor (now) | 2.00 | 1.80 | 1.60 | 1.40 |
| sandy_yclept/contact-page-finder, current entry | 1.00 | 1.00 | 0.90 | 0.80 |
| sandy_yclept/contact-page-finder, entry with startedAt 2026-11-02T17:00Z | 5.00 | 2.00 | 1.80 | 1.60 |
Notes, so the table is read correctly:
- The 2026-11-02 row is a scheduled price entry shown in that actor's
pricingInfoson the public API (with anotifiedAboutFutureChangeAtdate). It is not their announcement; I did not see the author's own statement and do not know their reasons. - That actor also lists a tiny per-run actor-start event on its page, not included above.
- The two actors do not charge for the same thing. contact-page-finder charges when a contact page or the page holding the form is found. Ours charges when at least one contact page URL, form URL, email, phone or social link is found. Sites with nothing found, failed or robots-skipped are not charged by either. If you only need page URLs, the cheaper per-site price may suit you better; if you also want emails, phones and socials in the same row, one run covers it.
- Prices only matter after your Apify free credits; compare on your own batch.
Limits
- Only public http(s) pages are fetched; respect robots.txt and the law when you use contact data (outreach rules such as CAN-SPAM and GDPR are your responsibility).
- The curl and Node snippets follow the actor's input schema; I have not run these exact snippets end to end, so test with a tiny list and a low
maxCostUsdfirst.
Written with AI (Claude).

