Quick answer
BuiltWith and Wappalyzer answer "what does this site run?" by sniffing front-end signals — JS libraries, tracking pixels, CDN headers. Neither can see Postgres, Kafka, or Kubernetes, because none of that ships to the browser. The BuiltWith Alternative Scraper reads the one place companies do publish their back-end stack in plain English — their own Greenhouse, Lever, and Ashby job postings — and regex-matches against a curated vocabulary of ~110 canonical tech names. Pricing is $0.005 per job row plus a $0.05 run-start charge: a 50-job run costs $0.30, and 1,000 rows costs $5.05.
Why front-end scanners are structurally blind to your stack 🔭
This isn't a coverage gap BuiltWith could patch with a bigger crawler. Postgres, Django, Kafka, Snowflake, and Kubernetes never touch the HTML response — they're server-side and internal-infra, with zero footprint in what a browser (or a browser-sniffing tool) ever sees. BuiltWith and Wappalyzer are excellent at what they do see — HubSpot, Segment, Cloudflare, React — but a subscription to either tells you nothing about whether a target account runs Django or Rails.
Companies do publish that information, though — just not on the marketing site. Engineering job postings routinely name the exact stack a team hires for: "You'll work with Python, Django, PostgreSQL, and AWS" is a normal sentence on a Greenhouse board. That's the surface this Actor reads instead: boards-api.greenhouse.io, api.lever.co, and api.ashbyhq.com — three ATS platforms' own public job-board APIs, normalized into one schema, then scanned with a compiled regex against the tech vocabulary.
The regex has to know Postgres beats Postgres SQL 🧩
A curated 110-term vocabulary sounds simple until two terms share a prefix. PostgreSQL contains Postgres as a substring; a naive first-match regex would tag every PostgreSQL mention as the shorter, less specific term and never surface the full name. Python's re engine is leftmost-first, not longest-match, so the fix has to happen before compilation: every alternative in the vocabulary is sorted by length, descending, before being joined into one alternation. PostgreSQL gets tried before Postgres, so the longer, more specific canonical name wins whenever both are present.
The same module also carries a quieter fix for punctuation-heavy tech names. C#, C++, and Next.js all end in characters that a standard \b word-boundary treats ambiguously — a trailing boundary after # or + can silently fail to match. The term-pattern builder drops the trailing boundary for any name ending in #, +, or ., so those names still match cleanly instead of vanishing from results.
Greenhouse double-encodes its own HTML, and Lever hides skills in list blocks 🔧
Two ATS-specific quirks get absorbed before a job description ever reaches the regex. Greenhouse wraps its content field in HTML entities twice — &lt;div&gt; where a single-encoded <div> would do — so the parser calls html.unescape() twice before stripping tags, or the raw entity soup would leak into every description and quietly suppress tech-name matches sitting inside it. Lever, separately, sometimes omits its "Requirements" bullet list from the flat descriptionPlain field entirely; the parser concatenates every lists[].content chunk from the structured payload to recover skills that would otherwise never reach the description text at all.
What we handle for you 🛡️
- We isolate failures per company. One bad token — a 404 from Lever, a typo on Ashby — does not abort the run; every other company in the batch still produces data.
-
We retry with exponential backoff on
408 / 429 / 503, up to 5 attempts per request, honoringRetry-After. - We validate every row with Pydantic before it lands — input and output both, so a malformed token fails fast instead of shipping a broken row.
- We fail loud, not silent. If every token is wrong and zero jobs come back, the run reports that plainly instead of a quiet green checkmark.
- You pay only for job rows that land. No data, no charge, beyond the small $0.05 run-start fee.
Full output schema 📦
Eleven fields per active job posting:
| Field | Type | Notes |
|---|---|---|
ats |
string |
greenhouse, lever, or ashby
|
company_token |
string | Board slug passed to the ATS |
job_id |
string | ATS-canonical job identifier |
title |
string | Job posting title |
location |
string | null | Location string (Remote, NYC, Berlin…) |
department |
string | null | Department or team |
url |
string | Public job-post URL |
description_text |
string | Plain-text description, HTML stripped and double-unescaped |
detected_techs |
string[] | Sorted, deduplicated canonical tech names, longest-match-first |
posted_at |
string | null | ISO-8601 UTC publication timestamp |
scraped_at |
string | ISO-8601 UTC row-creation timestamp |
Who this is for
B2B sales qualification — enrich an account list with the company's real back-end stack before your rep pitches a Postgres-tuning or Kubernetes-observability tool.
Recruiter sourcing — pull every senior backend role across target accounts and filter by detected_techs for stack-matched candidates.
Competitive intelligence — track which competitors are newly hiring for Kubernetes or Snowflake as an early roadmap signal.
CRM enrichment — a per-result alternative to a BuiltWith or TheirStack seat for the specific slice of technographics that only job posts reveal.
Frequently asked questions
What does this see that BuiltWith and Wappalyzer don't?
Back-end infrastructure — databases, queues, cloud providers, and orchestration tools like Postgres, Kafka, Snowflake, and Kubernetes — because those never appear in a browser response. Front-end scanners are structurally blind to them; this Actor reads them from the one place companies state them directly: their own job postings.
Why does the regex sort the tech vocabulary by length first?
Because PostgreSQL contains Postgres as a substring, and Python's regex engine matches leftmost-first, not longest. Without descending-length ordering, every PostgreSQL mention would get tagged as the shorter Postgres and the more specific name would never surface.
Why is Ashby's token case-sensitive?
Ashby's job-board API keys off the exact casing in the company's board URL — Ramp and PostHog work; ramp and posthog return zero jobs. Copy the token exactly as it appears in jobs.ashbyhq.com/{Token}.
What does 1,000 job rows cost?
$5.05 — 1,000 × $0.005, plus the $0.05 run-start charge.
Does one bad company token break the whole run?
No. Per-company fault isolation means a 404 or a typo on one token doesn't abort the batch — every other company you listed still produces rows.
Try it
Live on the Apify Store: BuiltWith Alternative Scraper.
Point it at a company's Greenhouse, Lever, or Ashby board and get back typed rows with the back-end stack read straight from the job description — no SaaS seat, pay-per-result.
Built by Devil Scrapes — we build scrapers for the targets that fight back.
Top comments (0)