SFOLN

Catalogue / Company & lead intelligence

Hiring Signals API – Open Jobs by Company

sfoln/hiring-signals · Jobs, Lead generation, Automation On request

Hiring signals for any company: give a domain, careers page or job board and get one snapshot of its open roles from Greenhouse, Lever, Ashby and 8 more ATS: counts by department, location and seniority, remote share, salary bands, new roles in 7/30/90 days, velocity. $8 per 1,000.

About

What it does

Get hiring signals for any company from its own job board: give a website domain, a careers page or a job board URL and get one snapshot of the company's open roles: how many, which departments, locations, countries and seniority levels, the remote share, salary bands per department, how many roles were posted in the last 7, 30 and 90 days, the most common titles and a hiring-velocity score. The job board is found automatically on Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Personio, Teamtailor, Breezy HR, BambooHR, Workable and Workday. Built for B2B sales teams (hiring is a buying signal), recruiters and staffing agencies, investors tracking growth, competitive intelligence and AI agents.

Price

Price per delivered record

EventPricePer 1,000Charged for
Hiring snapshot
hiring-snapshot
$0.008$8 / 1,000One company's hiring snapshot: open roles by department, location, seniority and workplace type, remote share, salary bands, roles posted in the last 7/30/90 days, top titles and a hiring-velocity score.

Apify free plan: up to 10 items per run. Paid plans have no per-run cap; set maxTotalChargeUsd to cap spend.

Run

Call it from code, an agent or the Console

This API is private on Apify for now. Email us and we will publish it for you, usually within two working days. The calls below work once it is public. contact@sfoln.com

curl · synchronous run, rows in the response
curl -X POST "https://api.apify.com/v2/actors/sfoln~hiring-signals/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["stripe.com","https://jobs.ashbyhq.com/ramp"]}'
mcp.json · hosted Apify MCP server
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/hiring-signals"
    }
  }
}
asynchronous run · for large batches
POST https://api.apify.com/v2/actors/sfoln~hiring-signals/runs?maxTotalChargeUsd=1
GET  https://api.apify.com/v2/actor-runs/{runId}            # until status is SUCCEEDED
GET  https://api.apify.com/v2/datasets/{defaultDatasetId}/items?format=json

Authenticate with an Apify API token. MCP clients can instead sign in with OAuth when prompted. Example prompt for an agent: Get hiring signals for stripe.com and ramp.com: how many open roles each has, which departments are growing, how many roles were posted in the last 30 days and their salary bands.

Input

Input fields

Add companies as website domains, careers pages, job board URLs or <ats>:<token>. Each company produces one hiring snapshot.

FieldTypeDefaultMeaning
companies *arrayrequiredCompanies (domains, careers pages or job boards)
One per line: a company website (stripe.com: the job board is found from its links, its careers page or its name), a careers page, a job board URL (https://jobs.lever.co/palantir, https://boards.greenhouse.io/stripe, https://jobs.ashbyhq.com/ramp …) or <ats>:<token> such as greenhouse:stripe or ashby:ramp. Duplicates are removed. Accounts on the Apify free plan can process up to 10 companies per run.
maxJobsPerCompanyinteger2000Max open roles analyzed per company
Open roles read per company (1–10,000). Boards larger than this get a snapshot of the first roles read and a warning. Range 1–10000.
includeJobsbooleanfalseInclude the job list
Add a jobs array (up to 500 roles: title, department, locations, workplace type, seniority, salary, posting date, URL) to each snapshot. No extra charge.
includeWorkdaybooleanfalseRead Workday career sites
Workday sites (*.myworkdayjobs.com) are read through an undocumented endpoint, at most 200 jobs per site and 1 request per second. Off by default: such companies are skipped (not charged).
guessBoardsbooleantrueGuess the board from the domain
When the website links to no supported job board, try the company's domain name as a board name on Greenhouse, Lever, Ashby, Recruitee, Personio, Teamtailor, Breezy, SmartRecruiters and BambooHR (snapshots say detectedBy: slug_guess). Not tried for IP addresses or sites on hosting platforms (*.vercel.app, *.github.io, *.wordpress.com …).
maxCompaniesinteger1000Max companies
Safety cap on the number of companies processed in this run. Range 1–10000.
concurrencyinteger4Concurrency
How many companies are processed in parallel (1–8). Requests to each ATS are rate-limited for the whole run. Range 1–8.
itemTimeoutSecsinteger300Time limit per company (seconds)
Reading one company's board stops cleanly shortly before this limit (60–3600); the snapshot then covers the roles read. Range 60–3600.
includeRawbooleanfalseInclude raw upstream response
Attach the ATS's original job objects (up to 200) to each snapshot (much larger output).
Example input
{
  "companies": [
    "stripe.com",
    "https://jobs.ashbyhq.com/ramp"
  ]
}

Output

One record per input item

Records land in the run's dataset; a synchronous call returns them directly. Each has a status (ok is charged; everything else is free) and a charged flag. The run's OUTPUT record totals items and charges.

FieldTypeMeaning
inputstring | objectInput
The company entry as given.
companystring | nullCompany
Company name from the board, else the board token.
companyDomainstring | nullCompany domain
The website given, else the most common company domain of the job URLs.
atsstring | nullATS
greenhouse | lever | ashby | smartrecruiters | recruitee | personio | teamtailor | breezy | bamboohr | workable | workday
boardTokenstring | nullBoard token
boardUrlstring | nullBoard URL
detectedBystring | nullDetected by
input (board given) | careers_page (linked from the website or careers page) | slug_guess (domain name tried on the ATS APIs)
otherBoardsarrayOther boards
Further boards the website linked to.
jobsCompletebooleanAll roles read
False when the board had more roles than maxJobsPerCompany or the time limit (a warning says so).
openJobsintegerOpen roles
departmentsCountintegerDepartments
byDepartmentobjectRoles by department
Department → open roles, most first (25 departments; the rest under "(other)").
byLocationobjectRoles by location
Location → open roles (a role with several locations counts in each; 20 locations, the rest under "(other)").
byCountryobjectRoles by country
ISO 3166-1 alpha-2 country → open roles ("unknown" when the location does not tell).
bySeniorityobjectRoles by seniority (guess)
intern, entry, mid, senior, lead, manager, director, vp, executive, unknown → open roles.
byWorkplaceTypeobjectRoles by workplace type
remote, hybrid, onsite, unknown → open roles.
byEmploymentTypeobjectRoles by employment type
full_time, part_time, contract, temporary, internship, volunteer, other, unknown → open roles.
remoteSharenumber | nullRemote share
Remote roles among roles with a known workplace type (0–1).
hybridSharenumber | nullHybrid share
Hybrid roles among roles with a known workplace type (0–1).
salaryCoveragenumber | nullSalary coverage
Share of open roles that publish a salary (0–1).
salaryRangeobject | nullSalary range
{ currency, min, max, median, jobs }: annualized salaries of all roles in the most common currency; a range more than 10× above or below the median of 3 or more ranges is left out.
salaryByDepartmentarraySalary by department
[{ department, currency, min, max, median, jobs }] annualized (hourly × 2,080, monthly × 12), up to 15 departments.
postedLast7DaysintegerPosted in the last 7 days
postedLast30DaysintegerPosted in the last 30 days
postedLast90DaysintegerPosted in the last 90 days
jobsWithPostedDateintegerRoles with a posting date
newestPostedAtstring | nullNewest posting
oldestPostedAtstring | nullOldest open posting
medianJobAgeDaysinteger | nullMedian role age (days)
topTitlesarrayTop titles
[{ name, count }]: the 10 most common titles without seniority words and suffixes.
newestJobsarrayNewest roles
[{ title, department, location, postedAt, url }]: the 5 newest roles.
hiringVelocityobject | nullHiring velocity
{ score 0–100, label none | low | moderate | high | very_high, basis posting_dates | open_roles }
jobsarrayJobs
Only with includeJobs: up to 500 roles { jobId, title, department, team, locations, country, workplaceType, employmentType, seniority, salary, postedAt, url }.
statusstringStatus
ok | not_found | error | skipped_invalid | skipped_budget | skipped_free_plan | skipped_unmetered e.g. "ok"
chargedbooleanCharged
Whether this record incurred a pay-per-event charge (hiring-snapshot).
chargedEventstringCharged event
Pay-per-event event charged for this record (present only when charged).
chargedCountintegerEvents charged
Events actually charged when the charge came back short of the requested count (present only then; a warning explains it).
sourcestringSource
Upstream data source (ats).
fetchedAtstringFetched at
ISO timestamp.
errorobjectError
{ code, message, provider?, status? } when status is not ok. Actor codes: invalid_input, board_not_found, workday_not_enabled. Upstream codes: not_found (unknown board), http_error (see status), timeout, network, dns_not_found, too_large, invalid_json, unexpected_type, invalid_response. Codes set by the runtime: budget_exhausted, free_plan_limit, unmetered, aborted, charge_failed, record_too_large, invalid_record, dataset_push_failed.
warningsarrayWarnings
Notes, e.g. a snapshot that covers only part of a very large board, or a website page that could not be read.
Example record
Example record
{
  "input": "https://jobs.ashbyhq.com/ramp",
  "company": "ramp",
  "companyDomain": null,
  "ats": "ashby",
  "boardToken": "ramp",
  "boardUrl": "https://jobs.ashbyhq.com/ramp",
  "detectedBy": "input",
  "jobsComplete": true,
  "openJobs": 162,
  "departmentsCount": 15,
  "byDepartment": {
    "Sales": 68,
    "Engineering": 37,
    "People & Talent": 13,
    "Product": 8,
    "Marketing": 7
  },
  "byLocation": {
    "New York, NY (HQ)": 139,
    "San Francisco, CA": 92,
    "Remote (US)": 46,
    "Miami, FL": 23
  },
  "byCountry": {
    "US": 144,
    "GB": 12,
    "CA": 6
  },
  "bySeniority": {
    "intern": 5,
    "entry": 8,
    "mid": 5,
    "senior": 18,
    "lead": 11,
    "manager": 24,
    "director": 7,
    "vp": 0,
    "executive": 0,
    "unknown": 84
  },
  "byWorkplaceType": {
    "remote": 14,
    "hybrid": 131,
    "onsite": 17,
    "unknown": 0
  },
  "byEmploymentType": {
    "full_time": 154,
    "part_time": 0,
    "contract": 0,
    "temporary": 3,
    "internship": 5,
    "volunteer": 0,
    "other": 0,
    "unknown": 0
  },
  "remoteShare": 0.086,
  "hybridShare": 0.809,
  "salaryCoverage": 0.957,
  "salaryRange": {
    "currency": "USD",
    "min": 14000,
    "max": 518000,
    "median": 217225,
    "jobs": 144
  },
  "salaryByDepartment": [
    {
      "department": "Sales",
      "currency": "USD",
      "min": 14000,
      "max": 518000,
      "median": 210000,
      "jobs": 55
    },
    {
      "department": "Engineering",
      "currency": "USD",
      "min": 134400,
      "max": 400000,
      "median": 250000,
      "jobs": 36
    }
  ],
  "postedLast7Days": 7,
  "postedLast30Days": 31,
  "postedLast90Days": 74,
  "jobsWithPostedDate": 162,
  "newestPostedAt": "2026-10-13T15:07:59.917Z",
  "oldestPostedAt": "2025-02-11T19:20:41.081Z",
  "medianJobAgeDays": 62,
  "topTitles": [
    {
      "name": "Account Executive",
      "count": 21
    },
    {
      "name": "Software Engineer",
      "count": 12
    }
  ],
  "newestJobs": [
    {
      "title": "Manager, Account Manager | Growth",
      "department": "Sales",
      "location": "New York, NY (HQ)",
      "postedAt": "2026-10-13T15:07:59.917Z",
      "url": "https://jobs.ashbyhq.com/ramp/190c265a-ca4f-48b7-b2a4-13412299249d"
    }
  ],
  "hiringVelocity": {
    "score": 41,
    "label": "moderate",
    "basis": "posting_dates"
  },
  "status": "ok",
  "charged": true,
  "chargedEvent": "hiring-snapshot",
  "source": "ats",
  "fetchedAt": "2026-10-14T12:00:00.000Z"
}

FAQ

Questions

How is the job board found from a domain?
The Actor reads the website and its careers page and looks for links and embeds of Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Personio, Teamtailor, Breezy, BambooHR, Workable and Workday. When none is linked it tries the domain name as a board name (detectedBy: slug_guess) and keeps a guess only when the board has jobs and its company name, where the ATS reports one, matches the domain.
What is the hiring-velocity score?
A 0–100 score: half from how many roles were posted in the last 30 days (reaching the maximum at 50 roles), half from the share of open roles posted in the last 30 days. Labels: none (0), low (under 25), moderate (under 50), high (under 75) and very_high. When a board gives no posting dates the score is based on the number of open roles (basis: open_roles).
Am I charged for companies without a job board?
No. Companies without a detectable board, unknown boards and errors return status not_found or error and are not charged. A company whose board exists but has no open roles gets a snapshot with openJobs: 0, which is charged.

Machine-readable

This page for agents