# B2B Account Research & Enrichment: Tech, Hiring, Contacts (`hirepulse/b2b-account-research-agent`) Actor

Company domain in, one compact profile out: website technologies, email provider, job board with live open-job count, and public emails, phones and social profiles. No browser, no login. Charged only for useful profiles.

- **URL**: https://apify.com/hirepulse/b2b-account-research-agent.md
- **Developed by:** [HirePulse](https://apify.com/hirepulse) (community)
- **Categories:** Lead generation, Jobs, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 company profiles

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

## B2B Account Research & Enrichment: Tech, Hiring, Contacts

Turn company domains into live hiring, website technology and email-provider signals.
Prioritize accounts using open-job counts, hiring pace, departments and source evidence.
Get one JSON row per company, with a separate status for every section.
$0.005 per useful profile; blocked, parked and DNS-only results are not charged.
Start with `{"domains": ["linear.app", "vercel.com"]}`; no third-party data-provider key required.

What each profile contains:

- **Hiring**: the company's job board (Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters) with the **live open-job count**, postings published in the last 7 and 30 days, open roles by function, `hiring_sales` / `hiring_marketing` / `hiring_engineering` flags, open leadership roles, and the **technologies named in its job postings**, each with a short quote.
- **Website technologies**: analytics, ad pixels (including those configured in Google Tag Manager), CMS, e-commerce, frameworks, payments, CDN.
- **How the company sells**, from its home page: pricing page, free trial or free plan, "book a demo" or "contact sales", security/trust page and the certifications it claims, integrations page, API docs, status page.
- **Email setup from public DNS**: mail provider (Google Workspace, Microsoft 365...), SPF senders, DMARC policy, verified services.
- **Public contacts**: emails, phones, LinkedIn and other social profiles published on the company's own website.
- The **languages and countries** the site is localized for.

No browser, no login, no paid data provider. Calling the Actor needs your Apify API token. Every part has its own status, so a blocked website is never reported as "no data", and a missing hiring count means **unknown, not zero**.

Built for sales and research agents (LangGraph, CrewAI, Claude/Cursor via MCP), n8n/Make/Clay workflows and CRM enrichment. Not for personal email discovery, mailbox verification, revenue or headcount.

### Quick start

```json
{ "domains": ["linear.app", "vercel.com"] }
```

One row per domain (real output for linear.app, 23 October 2026, shortened; hiring and sales signals from the pre-release build):

```json
{
  "domain": "linear.app",
  "status": "ok",
  "company_name": "Linear",
  "description": "Purpose-built for planning and building products with AI agents.",
  "tech_stack": ["Cloudflare", "Next.js", "React"],
  "mail_provider": "Google Workspace",
  "email_senders": ["Google Workspace", "Amazon SES", "HubSpot"],
  "dmarc_policy": "reject",
  "ats": {
    "detected": true, "platform": "ashby", "match": "linked", "evidence_url": "https://linear.app/careers",
    "active_jobs_count": 34, "remote_jobs_count": 34,
    "departments": [{"name": "GTM", "count": 19}, {"name": "Product", "count": 13}, {"name": "Operations", "count": 2}],
    "newest_job_posted_at": "2026-10-21T23:27:33.281000Z",
    "sample_titles": ["Product Support Specialist, APAC", "Creative Producer", "Field Marketing Manager"],
    "posted_last_7_days": 7, "posted_last_30_days": 10, "posting_date_source": "ashby.publishedAt",
    "role_counts": {"engineering": 9, "customer_success": 7, "sales": 5, "design": 4, "marketing": 3, "product": 2},
    "hiring_sales": true, "hiring_marketing": true, "hiring_engineering": true,
    "job_tech_mentions": [{"name": "GraphQL", "postings": 8,
      "evidence": "…new user-facing features with everything from database models to GraphQL resolvers and UI components - Optimize our data synchronization stack…"}]
  },
  "sales_signals": {"pricing_page": "https://linear.app/pricing", "signup_page": "https://linear.app/signup",
    "trust_page": "https://linear.app/security", "integrations_page": "https://linear.app/integrations",
    "contact_sales": "Contact sales", "motion": "hybrid"},
  "contacts": {
    "emails": ["hello@linear.app"], "phones": [],
    "x_url": "https://x.com/linear", "github_url": "https://github.com/linear", "youtube_url": "https://youtube.com/@linear",
    "contact_page": "https://linear.app/contact"
  },
  "sections": {"website": {"status": "ok"}, "dns": {"status": "ok"}, "technology": {"status": "ok"}, "hiring": {"status": "ok"}, "contacts": {"status": "ok"}},
  "duration_ms": 1343
}
```

Every row also has `charged` (true when the profile was billed, see below), `free_trial`, `note` and `scraped_at`.

The run summary (`OUTPUT` in the key-value store) counts profiles per status, charged profiles and measured seconds per domain.

### Input

| Field | Default | Meaning |
|---|---|---|
| `domains` | — | Company websites: `stripe.com`, `https://www.linear.app/pricing` or `jobs@vercel.com`. Up to 500 per run. |
| `sections` | all | Any of `technology`, `hiring`, `contacts`. DNS email signals are always included. |
| `depth` | `fast` | `fast`: home page plus one contact page. `thorough`: also about, team and imprint pages. Same price. |
| `max_sample_jobs` | 3 | Newest job titles to include (0–10). The job count always covers the whole board. |

Inputs of the earlier B2B Account Research version (`domain`, `maxResultsPerSource`, `sections` with `ads`) still work. Fields that no longer apply are ignored, with a note in the run summary.

### What is charged

One `company-enriched` event per profile that is **useful**. A profile is useful when the company website answered with a real page and at least one of these was found:

- a technology other than hosting/CDN,
- a public contact (an email at the company domain, a phone number or a social profile),
- a job board linked from the company's own pages (even one with zero open jobs).

**Never charged:** invalid domains, domains that do not exist, websites that did not answer or refused automated access, parked or for-sale pages, profiles with only DNS data or only a page title, and profiles not delivered because the run's maximum cost was reached. Every row has `charged: true/false`. Set a maximum cost per run in the run options; the Actor stops cleanly when it is reached.

- **Apify free plan:** the first 20 domains of each run are profiled at no charge; the rest are listed as `not_checked`.
- **Paid plans:** the first 20 useful profiles per account are free (`free_trial: true`). The counter lives in a key-value store this Actor creates in your account (`hirepulse-b2b-enricher-trial`); deleting it resets the counter.

### Statuses

| `status` | Meaning | Charged |
|---|---|---|
| `ok` | Website answered; every requested part was checked (a part may be empty). | if useful |
| `partial` | Website answered; at least one part timed out or its source failed. See `sections`. | if useful |
| `blocked` | The website refused automated access (HTTP 401/403/429/503 or a bot challenge). Not the same as "no data". | no |
| `unreachable` | No website answered, or it returned an error page. | no |
| `not_found` | The domain does not exist in DNS. | no |
| `parked` | Parked or for-sale page, not a company website. | no |
| `timeout`, `error` | The domain took too long or failed unexpectedly. | no |
| `invalid_input`, `not_checked` | Not a domain, or skipped (free-plan limit or maximum cost reached). | no |

Each part in `sections` (`website`, `dns`, `technology`, `hiring`, `contacts`) has its own status: `ok`, `empty` (checked, nothing found), `unavailable` (the source does not publish it), `timeout`, `error`, `blocked`, `not_checked` or `skipped`.

### How hiring data is attributed

Job counts come from the job boards' own public APIs. A board is attributed to a company only on evidence:

- **`match: "linked"`**: the company's home page or careers page links to or embeds the board. `evidence_url` shows where.
- **`match: "domain_evidence"`**: the website does not link it, but a board named after the company exists and its postings point to or name the company's domain (for example Greenhouse job URLs on `stripe.com/jobs`, or postings that mention `miro.com`).

A board with a matching name but no such evidence is **never used**. On 23 October 2026 the Greenhouse board named "ramp" belonged to a different company (1 posting), while Ramp's 171 jobs were on Ashby. The rule picks the right one; boards named "harbor" and "pulse" that belong to other companies were correctly rejected.

When no supported board is found, `active_jobs_count` is `null`: **unknown, not zero**. Workday, iCIMS, Taleo, SuccessFactors, BambooHR, Jobvite, Teamtailor and Personio boards are named in `platform` with `supported: false` and no count. Careers pages that load their job list only in the browser, with no board link in the HTML, are not covered.

The count is the number of open **postings** on the board. A role posted for several regions counts once per posting, so it can be higher than the number shown on a company's careers page. Linear: 34 postings on Ashby, 26 shown on linear.app/careers, all 26 included.

### Hiring signals

| Field | Meaning |
|---|---|
| `posted_last_7_days`, `posted_last_30_days` | Postings published in that window. `posting_date_source` names the job-board field used (for example `greenhouse.first_published`). A re-posted job can keep its old date, so this is posting age, not when the search began. `null` when the board publishes no dates. |
| `role_counts` | Open postings per function (sales, marketing, engineering, data, product, design, customer_success, finance, people, legal, operations, other), from job titles and departments. |
| `hiring_sales`, `hiring_marketing`, `hiring_engineering` | `true` when at least one open posting is in that function, `false` when none is, `null` when jobs are unknown. |
| `leadership_openings` | Up to 5 open VP, Head of, Director and C-level titles. |
| `job_tech_mentions` | Technologies named in the postings, with the number of postings and a short quote. Text repeated across most postings (company blurb, benefits, equal-opportunity statements) is ignored, so "our customers include..." lists do not count. |

Three distinctions stay explicit in the output:

- A technology **detected on the website** is not proof the company uses it internally; an ad pixel found in Google Tag Manager is a **configured** tag, not proof that it fires.
- A technology **named in a job posting** is not proof it is installed; the quote shows whether it is a skill, a partner or a customer.

### How the company sells

`sales_signals` reads the company's own home page. Page signals are the URL that shows them, and phrase signals are the words as written. `motion` is `self_serve` (pricing, free trial/plan or sign-up), `sales_led` (only demo or contact sales), `hybrid` (both) or `null`. `compliance_claims` lists the certifications the site mentions; it is a claim, not an audit. `site_locales` lists the languages and countries the site publishes localized versions for; that shows a localized website, not business in a country.

### Measured results

All on 23 October 2026, with the same 50 well-known company domains and `fast` depth. "Whole run" is what you wait for, including container start.

| Build | Environment | Run | Domains | Whole run | Platform usage |
|---|---|---|---|---|---|
| 1.0.1 | Apify cloud, 2 GB | `aH73X2dlIcy7ISpgZ` | 50 | 44 s | $0.0064 |
| 1.0.1 | Apify cloud, 2 GB | `vs93CInU1dcQwmeOo` | 1 (linear.app) | 3.8 s | $0.0006 |
| hiring and sales signals, pre-release | Docker on Linux, CPU limited like Apify (1 CPU per 4 GB), 2 GB | local | 50 | 80 s | — |
| hiring and sales signals, pre-release | same, 4 GB | local | 50 | 42 s | — |

Coverage in the pre-release run (48 profiles delivered; 1 website blocked, 1 domain does not exist):

| Signal | Found for |
|---|---|
| Useful (chargeable) profile | 45 of 50 |
| Open-job count from a job board | 26 of 48 (14 linked, 12 by domain evidence); 2 more on unsupported boards |
| Posting dates / a posting in the last 30 days | 26 / 23 of the 26 |
| Technologies named in postings | 24 of the 26 |
| Open leadership role | 18 of the 26 |
| Mail provider | 45 of 48 |
| Pricing page / demo or contact-sales / free trial or plan | 30 / 25 / 18 of 48 |

Accuracy checks, done by hand:

- **Job counts** matched the companies' own careers pages for Vercel (95), Calendly (9) and Zapier (12).
- **Role flags (precision):** in a fresh random sample of 40 titles per flag, from 18 job boards of well-known tech companies (about 8 of them not used while tuning the rules), `sales` was right for 39, `marketing` for 38 and `engineering` for 37. Two of the engineering misses were fixed after the sample. Accuracy on small businesses was not measured.
- **Sales-signal links:** 157 of 158 links opened. The one failure was a sign-up page that refuses non-browser requests.
- **Job-tech quotes:** 29 of 30 sampled named a skill or tool; 1 named a customer.

Timing on the Apify platform can differ; the run summary reports it for every run.

### Limits

- **Firmographics:** no headcount, revenue or funding data. `company_facts` shows only what the website publishes in schema.org data, and is not verified.
- **Contacts:** only what the company publishes on its own pages. No LinkedIn scraping, no guessed addresses, no mailbox verification (`accepts_email` means the domain has MX records). Phone numbers are taken from `tel:` links and schema.org only.
- **Advertising:** not included.
- **JavaScript-rendered sites:** websites built entirely in JavaScript can give partial technology and contact data.
- **Legal:** make sure your use of business contact data complies with the laws that apply to you, such as GDPR and CAN-SPAM.

### Use it from code, n8n or an AI agent

**cURL** (synchronous, returns the profiles):

```bash
curl -X POST "https://api.apify.com/v2/acts/hirepulse~b2b-account-research-agent/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' -d '{"domains": ["linear.app"]}'
```

**Python:**

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("hirepulse/b2b-account-research-agent").call(run_input={"domains": ["linear.app", "vercel.com"]})
for profile in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(profile["domain"], profile["ats"] and profile["ats"]["active_jobs_count"], profile["mail_provider"])
```

**MCP (Claude Desktop, Cursor and other agents):** add this Actor as a tool through the Apify MCP server at `https://mcp.apify.com`. Tool description for agents:

```text
Research company domains for live hiring counts from supported ATS boards, hiring pace and roles,
website technologies, how the company sells, DNS mail providers, and publicly published contacts.
Use for account prioritization and recurring account checks.
Returns one row per domain with section statuses and source evidence.
Not for personal email discovery, mailbox verification, revenue, or headcount.
Unknown hiring counts are null, not zero. Accepts up to 500 domains; free-plan runs process up to 20.
```

Example prompt: "Use hirepulse/b2b-account-research-agent on these domains. Return domain, open-job count, jobs posted in the last 30 days, hiring_sales, website technologies, mail provider and section statuses. Do not infer employee count, revenue, personal emails or missing job counts."

**n8n:** use the Apify node with "Run Actor and get dataset". Send `{"domains": {{ $json.domains }}}` and branch on `status` and `charged` in the rows.

# Actor input Schema

## `domains` (type: `array`):

Company websites, one per line: stripe.com, https://www.linear.app or an email like jobs@vercel.com. Up to 500 per run. Duplicates and paths are removed.

## `sections` (type: `array`):

Any of: technology (technologies and email DNS), hiring (job board and open jobs), contacts (public emails, phones, social profiles). Default: all three. Fewer sections are faster; the price per profile is the same. DNS email signals are always included.

## `depth` (type: `string`):

fast (default) reads the home page plus one contact page. thorough reads up to 8 pages per domain and is slower. The price per profile is the same.

## `max_sample_jobs` (type: `integer`):

How many of the newest job titles to include (0-10). The job count always covers the whole board. Default: 3.

## `domain` (type: `string`):

Kept so saved tasks and API calls from the earlier B2B Account Research version keep working. Prefer domains.

## `maxResultsPerSource` (type: `integer`):

Earlier name of max_sample_jobs. Used only when max_sample_jobs is not set.

## `use_paid_upstream` (type: `boolean`):

Must stay false. This Actor never calls paid data providers.

## `country` (type: `string`):

Ignored. The ads section is not part of this version.

## `maxChildEventChargeUsd` (type: `string`):

Ignored. This version runs no child Actors; set the run's maximum cost instead.

## `childTimeoutSecs` (type: `integer`):

Ignored.

## `mcpConnector` (type: `string`):

Ignored. Connector writes are not part of this version.

## `connectorOperations` (type: `array`):

Ignored.

## `allowConnectorWrites` (type: `boolean`):

Ignored.

## Actor input object example

```json
{
  "domains": [
    "linear.app",
    "vercel.com"
  ],
  "sections": [
    "technology",
    "hiring",
    "contacts"
  ],
  "depth": "fast",
  "max_sample_jobs": 3
}
```

# Actor output Schema

## `profiles` (type: `string`):

Dataset items: one row per domain, with a status for every part and whether it was charged.

## `runSummary` (type: `string`):

Counts per status, charged and free-trial profiles, and measured seconds per domain (p50/p95).

# 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 = {
    "domains": [
        "linear.app"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("hirepulse/b2b-account-research-agent").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 = { "domains": ["linear.app"] }

# Run the Actor and wait for it to finish
run = client.actor("hirepulse/b2b-account-research-agent").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 '{
  "domains": [
    "linear.app"
  ]
}' |
apify call hirepulse/b2b-account-research-agent --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hirepulse/b2b-account-research-agent"
        }
    }
}
```

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/X2Xxfa2HHG4zYbWHc/builds/TdnLhZ3oqqf7q87zO/openapi.json
