Skip to content
← Work

Web application · TypeScript

Master Job Board

Paste a company URL and it works out which applicant tracking system sits behind their careers page, then reads openings from that system's own public API. It does not scrape anyone.

5-stage discovery cascade
6 ATS adapters
Reads public APIs · scrapes nobody

In use · Next.js 16 · node:sqlite · TypeScript · Tailwind

The problem, stated narrowly

Job aggregators are stale. A posting shows up days late, or stays up weeks after it's filled, and there's no way to tell which from the outside.

The employer's own applicant tracking system doesn't have that problem — it's the system the posting was created in. So: paste a company's URL, work out which ATS sits behind their careers page, and read the openings from that system's own public API.

The constraint I set first

No scraping. Not because it's technically hard, but because it's against the terms of the sites that would matter most, and I wasn't going to build a job search tool for myself that I'd be uncomfortable explaining.

That constraint is why two of the biggest job sites are deliberately absent, and the reason is written into the project's own documentation rather than left for someone to notice.

Working inside that limit is what made the interesting part necessary.

The discovery cascade

Identifying the ATS is the actual problem, and it fails in a lot of different ways. So it's five stages, tried in order:

  1. The URL is already a direct ATS board — use it.
  2. Fetch the homepage and look for an outbound careers link.
  3. Fingerprint the returned HTML against known ATS markers.
  4. Probe likely board slugs derived from the company name.
  5. Fall back to structured job data or a syndication feed if either exists.

When all five fail, it does not return an empty board. It returns an error that lists what each stage tried and what came back — because an empty result and a failed lookup look identical to a user, and only one of them is worth retrying.

The part I'm most pleased about

There's no ORM, no job queue, no scraping framework, and no external database. The runtime's own built-in SQLite is enough, the scheduler is an interval, and the whole thing is one process.

Six ATS adapters, no ORM. It runs on a Mac mini on my own network.

The temptation with something like this is to reach for infrastructure that makes it look serious. Every piece of that infrastructure is a thing that can break at 3am for a tool whose entire job is showing me openings.

One detail that took a second pass

Different sources mean different things by "the job is gone."

A source returning a company's complete board is authoritative — a posting that disappears from it is closed, immediately. A source returning only the latest N postings is not: a job falls off that list because newer jobs appeared, not because it closed.

Treating those two the same marks live jobs as closed. They age out on last-seen date instead. That distinction is invisible until it's wrong, which makes it exactly the kind of thing the rest of this site is about.