All posts
Engineering · 2026-07-24 · 8 min read

How we shipped a bespoke ATS in eight weeks.

A play-by-play of the bespoke Applicant Tracking System build — from empty repo to a system that runs a 7,000-employee hiring pipeline. What we cut, what we kept, and what we'd do again.

An ATS is one of those systems everyone thinks they know until they build one. There's the candidate side (careers landing → apply form → status tracking), the recruiter side (queues, scoring, notes), the hiring-manager side (scorecards, decisions), and the CHRO / compliance side (audit trails, reports). Half a dozen roles, seven or eight statuses, and a schedule that has to survive interviewers going on holiday.

When our client came to us, they were running roughly 2,000 hires a year on Google Sheets, WhatsApp groups, and a shared Google Drive folder of PDF CVs. They didn't need software features — they needed one screen where the CHRO could see where every candidate was. Everything else fell out of that.

The three-week planning fight

The first three weeks were the hardest. Not because of engineering — because we had to argue our way out of a feature list that would have taken 30 weeks. The client's initial spec included candidate-portal login, LinkedIn OAuth, applicant referrals, ATS-to-LMS handoff, and a native mobile app. Each valuable, none of them essential to the "one screen" outcome.

We shipped a scope document with everything sorted into three columns: MVP, Phase 2, and Never (revisit later). The MVP column had exactly seven pipeline stages: intake, screening, shortlist, three interview rounds, offer. That was it. Everything else went to Phase 2 or the deferral bin.

Rule of thumb: if you can't cut a proposed feature and still have the system work end-to-end, it's MVP. Everything else is Phase 2.

The 8-week schedule

Once scope was locked, the sequencing was straightforward:

What we cut

Everything that wasn't on the critical path to "one screen":

What we kept

Three things we insisted on even when the timeline was tight:

1. Full audit trail from day one

Every state change, offer edit, and decision writes to an activity_log table with user, IP, and timestamp. When a candidate later asks "why was I rejected?" or a compliance auditor asks "who approved this offer?" — the answer is one query away. Retro-fitting an audit log to a running system is horrible; front-loading it costs a day of work.

2. Keyword-first scoring

The client asked for AI CV scoring on day one. We shipped a weighted keyword scorer as the default and left the LLM as an optional fallback. Reasons: the keyword scorer is explainable ("Strong: SMV, line balancing · Missing: ISO 9001"), zero-cost, offline, and — critically — defensible if HR gets audited. LLM scoring is the assist, not the primary driver.

3. Role-scoped queries

Every query in the app is filtered at the SQL level by the logged-in user's role and scope. HRBPs see their own applications, HODs see their plant, line managers see openings they raised. No JavaScript-level filtering, no "hide the button" security. If your endpoint returns something a role shouldn't see, you have a bug — full stop.

What we'd do again

  1. Ship the schema before the UI. Weeks 1 and 2 were mostly database and API work with barely visible progress. It hurt in demos but saved us four times over in weeks 5–8 when everything just clicked in.
  2. Weekly live demos. Every Friday we showed the client the working system. They spotted misunderstandings early. We had zero surprises at UAT.
  3. Migrations as code, not ad-hoc. Every DB change went through a name-keyed ensure_migrations() function that runs on bootstrap. Reproducing production locally = git pull && visit any page. Idempotent, boring, correct.
  4. One CSS design layer, not per-page styling. We built a legacy-upgrade CSS layer that transformed all existing card / table / pill markup to the new design tokens. Meant we could redesign 12+ pages in one afternoon.

What we'd do differently

Two things:

  1. Sooner testing on real CV data. Our seed CVs were engineered for the test — well-formed, clean text, target keywords in obvious places. Real candidate CVs are messy PDFs with two-column layouts and scanned images. When we finally ran the scorer against real submissions in week 6, the extraction quality was worse than expected. We fixed it, but we could have front-loaded that.
  2. Client education alongside build. The client had never used an ATS. Every review call spent 20% of time explaining a concept ("what's a shortlist review meeting?"). If we'd done a 90-minute training / vocabulary session in week 1, the review calls would have been more productive throughout.

The takeaway

Eight weeks isn't a heroic sprint. It's the correct duration when scope is locked, the team is senior-only, and someone whose job is saying no owns the roadmap. If you're staring at a 40-page spec for your ATS build, the first thing to do isn't hire more engineers. It's cross out 30 of the pages.

If you want a proper walkthrough of what we built, if you have your own ATS problem to size, book a call.