# Buyer Intent Signals API – Hiring & Missing-Tech Scoring (`siftsmith/buyer-intent-signals`) Actor

Score accounts 0-100 on buyer intent from public signals: active hiring on public ATS job boards plus a missing tech category you sell into (homepage detection). ICP lead scoring for Clay or your CRM. $0.06 per company with an open job board; no open board = free. Not third-party intent surge data.

- **URL**: https://apify.com/siftsmith/buyer-intent-signals.md
- **Developed by:** [Siftsmith](https://apify.com/siftsmith) (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.

It combines two Siftsmith Actors that already run in production, [Hiring Signals](https://apify.com/siftsmith/hiring-signals) (public ATS job-board data) and [Tech Enricher](https://apify.com/siftsmith/tech-enricher) (public homepage technology detection), and turns their two outputs into one decoded score instead of two raw dumps you'd have to cross-reference by hand.

### Quick start

Input:

```json
{ "companies": ["ramp.com", "stripe.com", "lever:palantir"], "targetCategory": "CRM" }
```

One row per company. A run on 2026-09-29 returned this for `ramp.com` (trimmed):

```json
{ "company": "ramp.com", "hiringFound": true, "ats": "ashby", "openJobs": 156, "postedLast30d": 59,
  "hiringScore": 68, "techFound": true, "hasTargetCategory": false, "buyerIntentScore": 100 }
```

Ramp is hiring fast and no CRM shows on its homepage, so its score is 68 × 1.5 = 102, capped at 100.

- **Price:** $0.06 per company with a public job board that has open jobs. Every other row is free, including no board, a board naming a different company, a failed job-board lookup and duplicates. There's no start fee. Details are under "Pricing".
- **Best input:** domains (`stripe.com`). A domain beats a bare name, because the tech half needs a real website. Workday boards must be passed explicitly (`workday:tenant:wdN:site`). Board syntax like `lever:palantir` has no domain, so it's scored on hiring alone (`techFound: false`).
- **Target category:** optional. Leave it out to score hiring alone.

### 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) |
| `bulkRepublish` | `true` when a board with 20+ open roles has more than half of them published within one 24-hour window (any 24 hours, so a burst across midnight UTC counts), the pattern of a board migration or re-import rather than a hiring surge (a brand-new board launched within 24 hours is flagged too). Those roles still count in `openJobs` and `postedLast30d` (the job board's own dates) but not as fresh in `hiringScore`, so a re-imported board doesn't read as surging intent |
| `openJobsCapped` / `openJobsReported` | `openJobsCapped` is `true` when the board has more open roles than one report covers (1,000 on Workday and SmartRecruiters); `openJobsReported` is then the job board's own total (`null` when it doesn't report one), and `hiringScore`'s freshness is taken of that total (see "Scoring") |
| `freshnessLowerBound` | `true` only on a capped board where every fetched role was posted in the last 30 days: `postedLast30d` and the freshness part of `hiringScore` are then lower bounds, same as in Hiring Signals |
| `matchConfidence` / `boardCompanyName` | How sure the job-board match is, same as Hiring Signals: `explicit` (you passed `ats:slug` or the board's URL), `high` (the board's own company name matches your input; for a domain, the board's website or job links are also on that domain), `medium` (unchecked: the board gives no name, e.g. some Ashby boards, or for a domain the name matches but the board lists no website to compare), `low` (it names a different company, its website is on a different domain, e.g. `linear.com` finding Ashby's "Linear" at linear.app, or it 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. On a `bulkRepublish` board, the roles published in the bulk 24-hour window don't count as fresh. On a capped board (`openJobsCapped: true`) the share is of the board's own total (`openJobsReported`), not of the 1,000 roles fetched: Workday and SmartRecruiters list newest roles first, so the fetched roles alone would over-state freshness (Bosch's first 1,000 of 4,801 are all recent: 100 before, 68 now, flagged `freshnessLowerBound`).

The scoring is fully transparent: a documented 1.5× boost when a company is both growing and missing the category you sell. Every score is explainable, so your team knows exactly why an account is at the top of the list.

### Input

- **Companies:** same format as Hiring Signals — names (`Ramp`), domains (`stripe.com`), or explicit boards (`greenhouse:stripe`, `lever:palantir`, `ashby:ramp`, `workable:huggingface`, `smartrecruiters:experian`, `breezy:attentive`, `personio:deskbird`, `workday:tenant:wdN:site`). A job board's own URL (`https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/resend`, `https://boards.greenhouse.io/stripe`, `https://salesforce.wd12.myworkdayjobs.com/External_Career_Site`, …) is read as that explicit board; like `ats:slug`, it has no company domain, so the tech lookup is skipped.
- **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/siftsmith/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 gives no name, as on some Ashby boards, or a domain's board lists no website to compare with it) is used and charged but couldn't be fully 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; so is an input whose lookup failed with an error. Set a max charge per run and the Actor stops cleanly when it's reached: every input not yet reported still gets a short free row with `skipped: true` and a `reason`, so no input goes missing from the dataset. You're never charged more than the max you set. A duplicate of a skipped input gets its own skipped row, never a pointer to a row with no data.

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. Exact repeats of the same text are collapsed to one row before processing.

### Use it in Clay

Clay has a built-in Apify integration, so this works as a per-row enrichment column:

1. In your Clay table, add an enrichment, search **Apify**, and choose **Run Apify Actor**.
2. Connect your Apify account with your Apify API token (Apify Console → Settings → Integrations).
3. Pick this Actor and enter the input as JSON, putting the row's value into the `companies` list. Clay's [Apify integration guide](https://university.clay.com/docs/apify-integration-overview) shows how to insert a column into the JSON.
4. Run the column. Each row's result comes back into Clay, where you can split fields such as `buyerIntentScore` into their own columns.

Billing: Apify charges $0.06 per company with an open job board (others are free) to your Apify account, with no per-run start fee. Clay counts the Action on its side but uses no Clay data credits when you bring your own Apify account.

### 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 detection as Tech Enricher (its DNS checks aren't used here). No logins, no scraping of personal data.

### Good to know

- **One run, two signals.** Uses the same detectors as Hiring Signals and Tech Enricher, cross-referenced and scored for you.
- **Best results with domains.** Bare company names are matched to a likely domain; passing the domain gives the most precise 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. Treat a high score as a strong lead to review.
- **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 roles reposted one at a time (a whole board re-published within 24 hours is caught: `bulkRepublish`).
- **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 hello@siftsmith.com. Issues are read daily and fixed promptly.

More tools from Siftsmith (formerly ToolFoundry): [siftsmith.com](https://siftsmith.com/).

# Actor input Schema

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

Company domains (best: 'stripe.com'), names (website guessed as {name}.com, which can be wrong) or explicit boards in Hiring Signals format: 'greenhouse:stripe', 'lever:palantir', 'ashby:ramp', 'workable:huggingface', 'smartrecruiters:experian', 'breezy:attentive', 'personio:deskbird', 'workday:tenant:wdN:site', or a board URL like 'https://jobs.lever.co/palantir'. Board syntax or URLs have no domain, so the tech signal is 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. Omit it 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("siftsmith/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("siftsmith/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 siftsmith/buyer-intent-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,siftsmith/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/bcywkviHel1oSzXCy/openapi.json
