# Buyer Intent Signals: Hiring + Tech Gap Scorer (`toolfoundry/buyer-intent-signals`) Actor

Score companies on buying intent: actively hiring (growth/budget proxy, public ATS boards) AND missing a tech category you sell into (public homepage/DNS detection). Combines Hiring Signals + Tech Enricher into one buyerIntentScore.

- **URL**: https://apify.com/toolfoundry/buyer-intent-signals.md
- **Developed by:** [ToolFoundry](https://apify.com/toolfoundry) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$60.00 / 1,000 company reports

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

## Buyer Intent Signals — Hiring Velocity + Tech Gap Scorer

Give it a list of companies. Get back one score per company: are they growing (actively hiring) **and** not yet using a category of tool you sell? That combination — growth without the category you're pitching — is a classic B2B buying-intent signal, the kind Bombora and 6sense charge four/five figures a month for.

This is v0.1. It doesn't add a new data source — it combines two ToolFoundry Actors that already run in production, [Hiring Signals](https://apify.com/toolfoundry/hiring-signals) (public ATS job-board data) and [Tech Enricher](https://apify.com/toolfoundry/tech-enricher) (public homepage/DNS technology detection), and turns their two outputs into one decoded score instead of two raw dumps you'd have to cross-reference by hand.

### What you get per company

| Field | Meaning |
|---|---|
| `hiringFound` / `hiringScore` / `openJobs` / `postedLast30d` / `ats` | Same hiring signal as Hiring Signals: is there a public job board, how many open roles, how many posted in the last 30 days, 0–100 hiring score (volume + freshness) |
| `matchConfidence` / `boardCompanyName` | How sure the job-board match is, same as Hiring Signals: `explicit` (you passed `ats:slug`), `high` (the board's own company name matches your input or its domain), `medium` (the board's name couldn't be read, e.g. some Ashby boards don't publish it, so it's unchecked), `low` (it names a different company, or is a Personio board with no name at all, so it's not used and not charged). See "Job-board matching" below |
| `techFound` / `technologies` / `technologiesByCategory` | Same tech-stack signal as Tech Enricher: detected technologies from the homepage/headers/CSP, grouped by category (CMS, CRM, Analytics, Marketing, Support, …) |
| `hasTargetCategory` | Only present when you set **Target technology category**. `true` if the detected stack includes that category, `false` if it doesn't (or if the tech lookup itself failed — check `techFound`/`techNote`) |
| `buyerIntentScore` | The combined score. See "Scoring" below for the exact formula — no hidden weights |
| `domain`, `domainGuessed` | The domain used for the tech lookup, and whether it was guessed from a bare company name (see "Input" below) |

### Scoring (exact formula, no black box)

```
buyerIntentScore = hiringScore                                   // no target category set
buyerIntentScore = hiringScore                                   // target category set AND detected in their stack
buyerIntentScore = round(hiringScore × 1.5), capped at 100        // target category set, tech lookup succeeded, AND category NOT detected
buyerIntentScore = hiringScore                                   // target category set but tech lookup failed (unknown, never guessed as an absence)
```

`hiringScore` is Hiring Signals' own 0–100 score: `round(100 × (0.6 × volume + 0.4 × freshness))`, where volume is a log-scaled open-role count (saturates ~300 roles) and freshness is the share of roles posted in the last 30 days.

The 1.5× boost is a simple, disclosed multiplier, not a trained or "proprietary" weighting — v0.1 is deliberately this plain rather than presenting a made-up formula as more rigorous than it is. It surfaces companies that are both growing and missing the category you sell, without pretending to know how likely they are to actually buy.

### Input

- **Companies:** same format as Hiring Signals — names (`Ramp`), domains (`stripe.com`), or explicit boards (`greenhouse:stripe`, `lever:palantir`, `ashby:ramp`, `workable:huggingface`, `smartrecruiters:visa`, `breezy:attentive`, `personio:deskbird`, `workday:tenant:wdN:site`).
- **Target technology category** (optional): one of the categories Tech Enricher's fingerprints (84 technologies) actually group technologies into, with the number of technologies in each: `Analytics` 15, `CMS` 11, `Frontend` 10, `Hosting` 9, `Marketing` 9, `Support` 7, `CRM` 5, `Ecommerce` 4, `Recruiting` 4, `Payments` 3, `Video` 3, `Fonts` 2, `A/B Testing` 2. Coverage is uneven: `CRM` only knows Salesforce, HubSpot CRM, Pipedrive, Zoho SalesIQ and Freshsales, and only when the homepage loads their script (a `<script src>` or the CSP; a link or a mention doesn't count). A company on any other CRM, or one whose CRM never appears on its homepage, shows `hasTargetCategory: false`. It's a dropdown, not free text, so a typo isn't possible via the Console; a raw API call with an unlisted value is rejected by the input schema before the run starts, not silently mis-scored.

#### Job-board matching

Company names and domains are matched to job boards the same way as in [Hiring Signals](https://apify.com/toolfoundry/hiring-signals): likely board names are tried on each supported ATS, and each board found is checked against the company name it gives for itself. A `low` match (the board names a different company, e.g. `Horizon` finding Lever's "Horizon Robotics") is not used as a hiring signal: the row is free, `hiringFound: false`, with that board's `ats`, `boardUrl` and `boardCompanyName` and an `error` saying to pass the board explicitly (`lever:horizon`) if it is the right company. A `medium` match (the board's name couldn't be read, as on some Ashby boards) is used and charged but couldn't be checked, so look at `boardUrl`. Large enterprises on Workday, SAP SuccessFactors, iCIMS or their own careers pages are often not found by name. Workday works only as an explicit `workday:{tenant}:{wdN}:{site}` board.

#### Domain resolution (read this if you pass bare company names)

The tech-stack half needs an actual website to fetch. If your input is already a domain (`stripe.com`) or explicit board syntax has no domain to give, it's used as-is or skipped. If your input is a bare company name (`Ramp`, `Hugging Face`), the domain is **guessed** as the name joined together + `.com` (`ramp.com`, `huggingface.com`) — the same slug-guessing approach Hiring Signals already uses for job boards. This works for a lot of companies but is not a real domain lookup; it will be wrong for companies whose brand name isn't their domain. `domainGuessed: true` flags every guessed case so you can spot-check. If a guessed domain redirects to a different company's site (e.g. `linear.com` goes to analog.com, not Linear), its tech data isn't used: `techFound: false` and `techNote` says where it redirected. A redirect is accepted when the site it lands on starts with the guessed name, e.g. `huggingface.com` → `huggingface.co`, `datadog.com` → `datadoghq.com`. Inputs that are IP addresses, `localhost` or private-network names are never fetched. **For accurate results, pass domains directly.** Explicit `ats:slug` inputs (`greenhouse:stripe`) have no domain information at all — the hiring signal still works, but the tech signal is skipped (`techFound: false`, `techNote` explains why).

### Pricing

Pay per result: **$0.06 per company with a public job board that has open jobs** (`hiringFound: true`). The score is built on the hiring signal, so without one `buyerIntentScore` is always 0. Companies with no board, a board with no open jobs, or only a `low`-confidence board (one that names a different company) are returned free, with any tech-stack data that was found, and an `error` saying why they weren't charged. Set a max charge per run and the Actor stops cleanly when it's reached.

Duplicates are charged once. Inputs are deduplicated on the job board they resolve to (ATS + slug), not on the text you typed: `Stripe`, `stripe.com` and `greenhouse:stripe` are one company, and so are `Linear`, `linear.app` and `ashby:linear`, even though they carry different domains (or none). Inputs with no job board are deduplicated on their domain. In each group the reported row is the first input, in input order, whose homepage lookup succeeded (so `linear.app` is reported rather than `ashby:linear`, which has no domain), or the first input if none did. Every other input in the group still gets its own row, free, with `duplicateOf` set to the reported input.

### Data sources & compliance

No new data source: reuses the same official public ATS job-board APIs (Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Breezy HR, Workday, Personio) as Hiring Signals, and the same public homepage HTML/headers/CSP + DNS-over-HTTPS lookups as Tech Enricher. No logins, no scraping of personal data.

### Limitations (v0.1, honestly)

- **Not a new data source.** This is a decoded/scored combination of two existing Actors' detectors, not an independent signal. If you already run both separately, this saves the cross-referencing step and adds the score — it doesn't see anything they don't.
- **Domain guessing for bare company names is a heuristic**, not a lookup (see above) — wrong for companies whose domain isn't `{name}.com`. Pass domains for reliable results.
- **Homepage-only tech detection**, same as Tech Enricher: a tool used only on a subpage (e.g. a CRM widget that only loads in a logged-in app) won't be detected, which can produce a false `hasTargetCategory: false` (and an inflated `buyerIntentScore`) for a company that actually has the tool. This is the single biggest accuracy risk in the scoring — treat a high score as "worth checking," not "confirmed."
- **Hiring-score inherits Hiring Signals' own limitations**: guessed job-board matches can miss companies on unsupported ATSes (enterprises on Workday, SuccessFactors, iCIMS or custom careers pages are often not found by name; Workday needs an explicit board), a same-named company's board can come out `high`, a `medium` match is unchecked, and `postedAt` freshness can be inflated by reposted roles.
- **No firmographic data** (employee count, revenue, funding) — none of that is available from public, unauthenticated sources.

### Support

Open an issue on the Actor's Issues tab or email toolfoundry@agentmail.to. Issues are read daily and fixed promptly.

# Actor input Schema

## `companies` (type: `array`):

Company names, domains, or explicit job-board syntax, same format Hiring Signals accepts: 'stripe.com', 'Ramp', 'greenhouse:stripe', 'lever:palantir', 'ashby:ramp', 'workable:huggingface', 'smartrecruiters:visa', 'breezy:attentive', 'personio:deskbird', 'workday:tenant:wdN:site'. A domain (e.g. 'stripe.com') gives the most accurate combined result; a bare company name's website is guessed as '{name}.com', which can be wrong, and explicit board syntax has no domain at all (hiring signal only, tech signal skipped).

## `targetCategory` (type: `string`):

Optional. The category of tool you sell (e.g. CRM, Analytics, Support). If set, companies whose detected tech stack does NOT include this category get a boosted buyerIntentScore, and every result gets a hasTargetCategory flag. Must be one of the categories Tech Enricher's fingerprints (84 technologies) actually use — leave blank to score by hiring activity alone.

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

How many companies to process in parallel.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "ramp.com",
    "Hugging Face"
  ],
  "targetCategory": "",
  "maxConcurrency": 5
}
```

# 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 = {
    "companies": [
        "stripe.com",
        "ramp.com",
        "Hugging Face"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("toolfoundry/buyer-intent-signals").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 = { "companies": [
        "stripe.com",
        "ramp.com",
        "Hugging Face",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("toolfoundry/buyer-intent-signals").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 '{
  "companies": [
    "stripe.com",
    "ramp.com",
    "Hugging Face"
  ]
}' |
apify call toolfoundry/buyer-intent-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,toolfoundry/buyer-intent-signals"
        }
    }
}
```

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/xZnUJFjeDQnxZbgpf/builds/vVTU8sgyPZOqirWds/openapi.json
