Jobs & Companies by Tech Stack — Greenhouse/Lever/Ashby avatar

Jobs & Companies by Tech Stack — Greenhouse/Lever/Ashby

Pricing

from $0.00005 / actor start

Go to Apify Store
Jobs & Companies by Tech Stack — Greenhouse/Lever/Ashby

Jobs & Companies by Tech Stack — Greenhouse/Lever/Ashby

Fetches postings from public Greenhouse, Lever, and Ashby job-board APIs, extracts the technologies each posting mentions, filters jobs by stack, and aggregates companies by technology for B2B lead generation.

Pricing

from $0.00005 / actor start

Rating

0.0

(0)

Developer

Eonix Pvt Ltd

Eonix Pvt Ltd

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

9 days ago

Last modified

Share

Crawls the public job-board APIs of Greenhouse, Lever, and Ashby for a list of companies, extracts the technologies every posting mentions (455-entry curated dictionary + optional LLM fallback), and emits two datasets in one run:

  1. Job postings filtered by stack — e.g. "remote jobs that mention Rust AND Kafka".
  2. Companies aggregated by technology — e.g. "which of these 50 companies are hiring people who use Snowflake, and how many open roles mention it" — ready for B2B prospecting and market research.

No browser, no scraping of HTML pages, no proxies needed: these are official public JSON endpoints, fetched politely (per-host concurrency limit + request spacing + exponential backoff).

Who it's for

  • Job seekers hunting roles by the stack they love, not just the title.
  • B2B sales / lead-gen teams building account lists of companies that demonstrably use (and are investing in) a technology.
  • Analysts & founders tracking stack adoption trends across a market.

How it works

  1. For each company you provide, the Actor resolves an ATS board:
    • If you pass a board token (greenhouse, lever, or ashby), it is used directly.
    • If you pass only a careersUrl, token candidates are derived from the URL (host and path) and probed Greenhouse → Lever → Ashby; the first endpoint that answers with a valid board wins. The detected ATS is recorded on the output (atsSource: "auto-detected").
  2. All postings are fetched in a single request per board and normalized to one shape (company, ats, jobId, title, department, location, remote, url, publishedAt, descriptionHtml).
  3. Technologies are extracted with word-boundary matching against a vendored dictionary of 455 canonical technologies (languages, frameworks, databases, cloud, devops, data, frontend, mobile, AI) with alias handling (golang→Go, k8s→Kubernetes, postgres→PostgreSQL) and longest-match-first logic (React Native never double-counts as React). Ambiguous names are guarded — Go never fires inside "Google" or "go-to-market", Swift ignores "SWIFT payments", Phoenix/Kong never fire on city names.
  4. Optionally, postings where the dictionary found fewer than 2 technologies are sent to OpenAI in batched structured calls (only if you provide openaiApiKey); merged hits are flagged source: "llm".
  5. Postings matching your filters are pushed as kind: "job" records; per-company aggregates are pushed as kind: "company" records and also written to the key-value store as companies-by-stack.json. A final kind: "summary" record reports totals, failures, and charges.

Input

FieldTypeDefaultDescription
companiesarray (required)Companies to crawl, see shape below. Max 100 per run.
requiredTechstring[][]Keep only postings mentioning ALL of these (aliases understood).
anyTechstring[][]Keep only postings mentioning AT LEAST ONE of these.
titleIncludesstring[][]Keep only postings whose title contains one of these substrings (case-insensitive).
remoteOnlybooleanfalseKeep only remote postings (ATS remote flag where available, else heuristic).
maxPostingsPerCompanyinteger500Cap on normalized postings per company (also caps billing).
openaiApiKeysecret stringEnables the LLM fallback for low-hit postings. Without it, no external LLM calls are ever made.
openaiModelstringgpt-4o-miniModel for the LLM fallback.
proxyConfigurationobjectnoneOptional Apify Proxy settings; not needed for these public APIs.

Each item of companies:

{
"name": "Ramp", // optional display name
"greenhouse": "ramp", // Greenhouse board token, OR
"lever": "ramp", // Lever board token, OR
"ashby": "ramp", // Ashby job-board name, OR
"careersUrl": "https://jobs.ashbyhq.com/ramp" // any careers URL — ATS auto-detected
}

At least one of greenhouse / lever / ashby / careersUrl is required per company.

Output

kind: "job" — one record per posting that passed your filters

Real record from a run with the default sample input (descriptionHtml shortened here — the dataset contains the full posting HTML):

{
"kind": "job",
"company": "GitLab",
"ats": "greenhouse",
"jobId": "8695515002",
"title": "Backend Engineer, Geo Team",
"department": "Platforms Engineering",
"location": "Bangalore, India",
"remote": false,
"url": "https://job-boards.greenhouse.io/gitlab/jobs/8695515002",
"publishedAt": "2026-08-12T03:33:39.000Z",
"technologies": [
{ "name": "GitLab", "category": "devops", "source": "dictionary" },
{ "name": "PostgreSQL", "category": "database", "source": "dictionary" },
{ "name": "Ruby on Rails", "category": "framework", "source": "dictionary" },
{ "name": "Sidekiq", "category": "framework", "source": "dictionary" }
],
"technologiesSummary": "GitLab, PostgreSQL, Ruby on Rails, Sidekiq",
"descriptionHtml": "<div class=\"content-intro\"><p>GitLab is the intelligent orchestration platform for DevSecOps. …</p>…"
}

kind: "company" — one aggregate per company (always covers ALL fetched postings, not just filtered ones)

Real record from the same run (66 technologies total; the top 5 shown here):

{
"kind": "company",
"company": "Ramp",
"ats": "ashby",
"atsSource": "input",
"careersUrl": "https://jobs.ashbyhq.com/ramp",
"totalPostings": 136,
"matchedPostings": 33,
"technologies": [
{
"name": "Python",
"category": "language",
"postingCount": 27,
"sampleJobUrls": [
"https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"https://jobs.ashbyhq.com/ramp/f564dcf9-9390-4a3f-896f-8047a5086040",
"https://jobs.ashbyhq.com/ramp/4745807e-82f4-4b1a-857c-dc8dadc73076"
]
},
{ "name": "SQL", "category": "language", "postingCount": 21, "sampleJobUrls": ["…"] },
{ "name": "Salesforce", "category": "cloud", "postingCount": 18, "sampleJobUrls": ["…"] },
{ "name": "LLMs", "category": "ai", "postingCount": 17, "sampleJobUrls": ["…"] },
{ "name": "Claude", "category": "ai", "postingCount": 13, "sampleJobUrls": ["…"] }
]
}

kind: "summary" — one final record per run

{
"kind": "summary",
"companiesProcessed": 4,
"companiesFailed": [],
"totalPostingsNormalized": 606,
"jobRecordsPushed": 248,
"companyRecordsPushed": 4,
"charging": {
"postings1kUnitsDue": 1,
"postings1kUnitsCharged": 1,
"companyProfilesCharged": 4,
"note": "Counts reflect accepted Actor.charge() calls. On local/unmonetized runs the platform ignores them (no money moves); real billing starts once the PPE events exist in the Apify Console."
},
"companiesByStackUrl": "https://api.apify.com/v2/key-value-stores/<store-id>/records/companies-by-stack.json"
}

A formal JSON Schema for all three record kinds ships in docs/output.schema.json.

Key-value store artifact

All company aggregates are additionally written to the run's key-value store as companies-by-stack.json (a JSON array), so you can grab the whole companies-by-technology dataset with one URL — logged at the end of every run and included in the summary record.

Pricing (pay-per-event)

EventWhat you pay forSuggested price
postings-1kEach 1,000 job postings fetched and normalized (rounded up once per run)$1.00
company-profileEach company aggregate profile produced$0.01

Example: 4 companies / 606 postings = 1 × postings-1k + 4 × company-profile = $1.04. You are never charged for failed work — charges happen only after the corresponding records are pushed, and maxPostingsPerCompany caps the volume per company.

Use cases

  1. Job search by stackanyTech: ["Rust"], requiredTech: ["Kafka"], remoteOnly: true across 30 companies you admire → every remote Rust job at companies also running Kafka, with links.
  2. B2B lead generation — feed 100 target accounts and look at the companies-by-stack.json artifact for "Snowflake" → companies actively hiring people to use Snowflake, with proof (sample job URLs) for your outreach email.
  3. Market research — run monthly over the same cohort and diff postingCount per technology to see which stacks are growing across your market.

FAQ

Which ATSes are supported? Greenhouse, Lever (US + EU clusters), and Ashby — via their public job-board APIs. Companies on other ATSes fail gracefully with a clear reason in the summary record.

How reliable is auto-detection from careersUrl? URLs hosted on the ATS itself (boards.greenhouse.io/…, jobs.lever.co/…, jobs.ashbyhq.com/…) are exact. For company domains the Actor guesses tokens from the domain and path; when in doubt, pass the board token explicitly.

Do filters affect the company aggregates? No. requiredTech/anyTech/titleIncludes/remoteOnly shape the kind:"job" records only; aggregates always describe the company's whole board (up to maxPostingsPerCompany).

How accurate is the extraction? The dictionary matcher is precision-tuned: word boundaries, longest-match-first, case-sensitive handling for ambiguous names (Go, Swift, Spark…), and phrase-only matching for landmine words (Phoenix, Kong, Nomad, Bedrock…). It only reports technologies literally mentioned in the posting. The optional LLM fallback catches postings phrased unusually; its hits are flagged source: "llm" so you can filter them out if you want dictionary-only precision.

Does it collect personal data? No — see the note below.

What happens when a board is huge? One HTTP request still fetches it; maxPostingsPerCompany (default 500) caps what is normalized, pushed, and charged.

Can it crawl arbitrary career pages without an API? No — that is out of scope by design. Supporting the three public APIs keeps the Actor fast, cheap, and reliable.

Data & GDPR note

The output contains job postings and company-level aggregates only — data the companies themselves publish for distribution via their ATS APIs. The Actor never collects recruiter names, e-mails, applicant data, or any other personal data. The optional LLM fallback sends only posting text (title + description) to OpenAI, and only when you supply your own API key.

Local development

npm install
npm test # build + unit tests (matcher fixtures, normalizers, charging math)
npx apify run # full local run against the sample INPUT.json (3 tokens + 1 auto-detected)

Results land in storage/datasets/default/ and storage/key_value_stores/default/companies-by-stack.json. Lint with npm run lint.

Deploy

apify login
apify push

Then in Apify Console → your Actor → Monetization: create the two pay-per-event events exactly as named in the pricing table above (postings-1k, company-profile), do a test run, and publish to the Store with this README as the store page. Details in RUNBOOK.md.

Changelog

0.1.0 (2026-08-14)

  • Initial release: Greenhouse/Lever/Ashby fetch + normalize, ATS auto-detection from careers URLs, 455-technology dictionary extraction with alias/ambiguity handling, optional OpenAI fallback, stack filters, company aggregates + companies-by-stack.json artifact, pay-per-event charging.