Most people think a side project needs a landing page, a waitlist, and a launch tweet. I shipped mine as a JSON endpoint on a marketplace, and eleven strangers started paying for it within a month. Here’s the full story — including the bug that almost cost me a customer.
The Idea
Recruiting teams and hiring-intelligence tools all face the same problem: every company posts jobs on a different system. Greenhouse, Lever, Ashby, Workday, SmartRecruiters — each has its own API, its own schema, its own quirks. If you want to track what a competitor is hiring for, you either write five scrapers or pay for an expensive data subscription.
I built ats-jobs-api to solve that: one actor that scrapes a company’s career page, detects the ATS behind it, and returns a normalized job feed. Same shape for every company, whether they run Greenhouse or Workday.
The Architecture
The actor is a Node.js crawler with pluggable adapters:
- Greenhouse — public job board API
- Lever — public job board API
- Ashby — public job board API
- Workday — HTML scraping with detail-page enrichment
- SmartRecruiters — public job board API
Each adapter normalizes into a single schema: title, location, department, employment type, description, posting URL, and a per-company state hash so re-runs only emit new jobs. That last part matters more than it sounds — it’s what makes the actor usable as a daily monitor instead of a one-shot scrape.
Pricing It Like a Utility
I priced it per job scraped, not per run:
- Start: $0.00005
- Job scraped: $0.004 (the primary charge)
- Company monitored: $0.02
Why per-job? Because a run over a 50-job company and a run over a 2,000-job enterprise are completely different workloads. Flat per-run pricing would either overcharge the small customer or bankrupt me on the big one. Per-job pricing means the customer pays for exactly what they consume — and my costs scale with revenue.
The Bill-Shock Bug
Here’s the part I almost learned the hard way.
The actor had a maxJobsPerCompany input, but no global cap. The default was unlimited. So when a user ran it for the first time on a company with 1,100+ open jobs, the actor dutifully scraped every single one — and charged them ~$4.89 in one run. The user aborted at 2.5 minutes, but the damage was done.
That’s not a code bug. That’s a pricing bug — and it’s the kind that kills trust in a marketplace product. A user’s first run should never be their most expensive run.
The fix: a maxTotalJobs input with a sane default, documented clearly, plus a warning in the README about first-run backfills. Now the worst case is bounded, and the user chooses if they want the full backfill.
What the Numbers Look Like
As of this writing:
- 11 users — real external usage, not just my own tests
- 48 runs — 41 succeeded in the last 30 days, 0 failed
- 5 ATS adapters — all live-verified against real companies
- 99% success rate on recent runs
Not a unicorn. But it’s a real product with real users, shipped in the open, generating revenue on a per-job basis.
What I’d Do Differently
- Ship the global cap from day one. The bill-shock bug was a trust near-miss. Bounds on cost are a feature, not a limitation.
- Version control from day one. The actor lived only on Apify for months — one local copy, no git repo. That’s a maintenance risk I’ve since fixed.
- Verify README examples against reality. The Lever adapter’s README listed Duolingo, Notion, and Figma as examples — all of which have since moved off Lever (198 companies migrated away, per my research). The API works; the examples were stale. Real examples beat aspirational ones.
Why Bother?
Because the best marketing for a technical agency is a working product. This actor is my proof-of-work: it demonstrates that I can design a data pipeline, price it sensibly, handle edge cases, and operate it in production. Anyone can claim they understand APIs and automation. This one has 48 runs of evidence.
If you’re building something similar — or you want a normalized job feed for your own tooling — the actor is live on Apify. And if you’re a Malaysian business wondering what your own “built in the open” could look like, that’s exactly the kind of conversation I like having. Let’s talk.