How I auto-match and rank jobs with n8n and AI

The short version

To match and rank jobs with n8n and AI, you run two schedules. An ingest branch crawls free job boards (Greenhouse, Lever, Ashby, Adzuna, RemoteOK) into one shared pool every few hours with no AI, so it stays cheap. A daily branch loops each seeker, pulls a short list, and lets an AI model score every job from 0 to 100 before it emails their best matches.

I built this because I was skimming a dozen job boards for a handful of people and losing the good roles in the noise. This is a real template I run. Below I walk the actual nodes, the parts that took the most fiddling, and how to point it at your own seekers.

The problem

Good jobs are spread across dozens of boards, and each one only shows its own.

If you are looking for a role, or matching roles for a group of people, the openings sit on Greenhouse, Lever, Ashby, LinkedIn, and a dozen niche boards. No single site shows them all. So you check the same boards over and over, skim past roles that do not fit, and still miss the good ones that went up an hour ago. Do this for more than one person and it turns into a part-time job on its own.

It adds up. The average US job search runs close to 20 weeks, and roughly 80 percent of openings are found online, spread across boards that do not talk to each other.

How most teams deal with it today

There are three common ways people find matching jobs today. Each has a catch.

Scroll job boards by hand

Free and simple. You open LinkedIn, Indeed, and a few company pages and read. But it eats an hour a day, the best roles hide in the noise, and you always miss the ones posted while you were not looking.

Job-alert emails

Most boards will email you when a new role matches a saved search. Handy, but the alerts key off simple keywords, so you get near-misses and repeats. Nobody ranks them for real fit, and ten alert feeds are their own inbox mess.

Hand it to a recruiter

A recruiter or a virtual assistant can shortlist for you. That works, but it costs money every month, they only know the roles they see, and a busy week means your list shows up late or not at all.

The fix

One flow crawls the boards for you, then an AI model ranks what fits.

A single n8n flow does the reading for you. Every few hours it crawls free job boards into one shared pool. Once a day it walks each seeker's profile, pulls a short list, and lets an AI model score every job for real fit before it emails only the best ones. Here is what it does, in three parts.

Crawls free boards into one pool

Every 3 hours the ingest branch hits the Greenhouse, Lever and Ashby board APIs plus Adzuna and RemoteOK, shapes each posting the same way, drops anything older than 90 days, and saves it into one shared jobs pool keyed on id. There is no AI here, so it is cheap to run often.

Scores each job against a profile

Once a day the match branch loops every active seeker, pulls a short list from the pool, and sends it to an AI model in batches of 20. The model returns a 0 to 100 fit score, a one-line reason, and any concerns for each job, so the ranking reflects real fit and not keyword stuffing.

Emails only the top matches

It keeps jobs at or above that seeker's min_score, which is the lowest score they want to see, sorts them high to low, saves them so they never repeat, and emails a digest with the score, role, company and an apply link. Each person gets a tight ranked list instead of a firehose.

Loading workflow…
Free download

Want this running for you?

Take the file and set it up yourself, or book a free call and we wire it into your sources, database and inbox for you.

Get your free audit

Build it step by step

Every node in the flow, in execution order, and how to set each one up. The ingest branch fills the pool, the match branch scores and emails, and a small error branch warns you if a run fails.

  1. 1
    Every 3 hours · Schedule Trigger

    Starts the ingest crawl on a timer so the shared pool stays fresh.

    Set it up: Add a Schedule Trigger and set the interval to every 3 hours. This is the only trigger the ingest branch needs.

  2. 2
    Search terms · Set

    Holds the list of roles the crawl searches for.

    Set it up: Add a Set node with one array field called queries. Put your job titles in it, like software engineer, product manager, and data analyst.

  3. 3
    ATS board list · Code

    Lists the company career boards to crawl.

    Set it up: Add a Code node that returns one item per company, each with an ats name (greenhouse, lever, or ashby) and a company slug. Add or remove companies to fit your search.

  4. 4
    Fetch ATS board · HTTP Request

    Pulls the open jobs from each company board.

    Set it up: Add an HTTP Request that builds the board URL from the ats and slug. Turn on retries and set neverError so one dead board does not stop the run.

  5. 5
    Parse ATS · Code

    Cleans each posting into one common shape.

    Set it up: Add a Code node that strips the HTML tags out of the text and maps every posting to the same fields: id, title, company, location, url, and description.

  6. 6
    Adzuna per query · Code

    Turns each search term into its own Adzuna request.

    Set it up: Add a Code node that reads the queries array from Search terms and returns one item per term.

  7. 7
    Adzuna search · HTTP Request

    Searches the Adzuna job API for each term.

    Set it up: Add an HTTP Request to the Adzuna search endpoint. Pass your free app id and app key, the search term, and ask for up to 50 results each.

  8. 8
    Parse Adzuna · Code

    Maps Adzuna results into the same shape as the rest.

    Set it up: Add a Code node that reads the results array and writes the same fields the other sources use.

  9. 9
    RemoteOK · HTTP Request

    Pulls the whole RemoteOK feed in one call.

    Set it up: Add an HTTP Request to the RemoteOK API. It is open, so it needs no key.

  10. 10
    Parse RemoteOK · Code

    Maps each remote posting into the common shape.

    Set it up: Add a Code node that skips empty rows and writes the same fields, with location set to Remote.

  11. 11
    Merge sources · Merge

    Joins the three source branches into one stream.

    Set it up: Add a Merge node with 3 inputs and wire Parse ATS, Parse Adzuna, and Parse RemoteOK into it.

  12. 12
    Normalize + freshness · Code

    Removes repeats and old postings before the pool write.

    Set it up: Add a Code node that drops any job it has already seen by id, throws out postings older than 90 days, and flags the rest as fresh.

  13. 13
    Upsert to jobs pool · HTTP Request

    Saves every job into the shared Supabase pool without making copies.

    Set it up: Add an HTTP Request that posts the rows to your Supabase jobs table with on_conflict set to id. That writes new jobs and updates ones already there, so the pool never doubles up.

  14. 14
    Daily 08:00 · Schedule Trigger

    Starts the matching run once a day.

    Set it up: Add a Schedule Trigger set to a daily cron of 8am. This kicks off the per-seeker scoring.

  15. 15
    Get active seekers · HTTP Request

    Reads the people you want to match jobs for.

    Set it up: Add an HTTP Request that reads the active rows from your Supabase job_seekers table, pulling each person's email, profile, function, and min_score (the lowest score they want to see).

  16. 16
    Loop Over Users · Split In Batches

    Handles one seeker at a time.

    Set it up: Add a Split In Batches node with batch size 1. Everything after it runs once per seeker, then loops back for the next one.

  17. 17
    Pool shortlist · HTTP Request

    Grabs a small set of likely jobs for this seeker.

    Set it up: Add an HTTP Request that reads up to 15 fresh jobs from the pool whose title matches the seeker's function, newest first. This cheap filter keeps the AI off the whole pool.

  18. 18
    Already-matched ids · HTTP Request

    Finds jobs this seeker was already sent.

    Set it up: Add an HTTP Request that reads every job_id already saved for this seeker so the next step can skip them.

  19. 19
    Make scoring batches · Code

    Builds the requests the AI model will score.

    Set it up: Add a Code node that drops the already-matched jobs, splits the rest into batches of 20, and packs each batch with the seeker's profile and a strict answer format for the model.

  20. 20
    Claude score batch · HTTP Request

    Asks the AI model to score each job from 0 to 100.

    Set it up: Add an HTTP Request to the Anthropic API. My build calls the claude-haiku-4-5 model. Turn on retries so a slow call does not fail the run.

  21. 21
    Attach scores + filter · Code

    Matches each score back to its job and keeps the good ones.

    Set it up: Add a Code node that puts the model's scores onto the jobs by index, keeps any job at or above the seeker's min_score, and sorts them from high to low.

  22. 22
    Save matches · HTTP Request

    Records which jobs this seeker was matched to.

    Set it up: Add an HTTP Request that writes the kept jobs to the user_job_matches table. Set it to ignore duplicates on user and job, so the same job is only ever saved once per person.

  23. 23
    Email digest · HTTP Request

    Emails the seeker their ranked matches.

    Set it up: Add an HTTP Request to the Resend API that builds an HTML list of the scored jobs and sends it to the seeker. Wire its output back into Loop Over Users so the flow moves to the next person.

  24. 24
    On workflow error · Error Trigger

    Catches any run that fails.

    Set it up: Add an Error Trigger. Set this workflow as its own Error Workflow in Settings so a failed run lands here.

  25. 25
    Format alert · Code

    Writes a short failure email.

    Set it up: Add a Code node that reads the failing workflow name, the node that broke, and the error message, then builds an alert email from them.

  26. 26
    Send alert (Resend) · HTTP Request

    Emails you the failure alert.

    Set it up: Add an HTTP Request to the Resend API that sends the alert to your inbox. Turn on retries in case the send itself hiccups.

What made it tricky?

Three things ate most of the build time. If you rebuild this, they are where it goes wrong first.

Keeping the AI cheap

The easy mistake is to score the whole pool. I keep the AI out of ingest, and the Pool shortlist node pre-filters to 15 fresh jobs per seeker with plain SQL before any scoring. The model only ever sees a small, relevant list, 20 jobs at a time.

Structured scores, not prose

A model that free-writes is impossible to rank. Make scoring batches hands the model a strict answer format, so each job comes back with an index, a score, a fit line, and any concerns. Attach scores and filter reads those numbers, keeps the ones over min_score, and sorts them. That is what turns AI text into a real ranking.

The loop-back wiring

Loop Over Users has two outputs. Output 0 means done, and output 1 is the per-seeker path. The last node, Email digest, has to connect back into the loop, not into Done. Get it backwards and it either scores one seeker or never moves on. Batch size 1 keeps each seeker's scoring and email apart.

How do I adapt it to my own setup?

The three branches are the working template. Adapting it means swapping sources, profiles and keys, not rewiring the logic.

To changeWhat you edit
The roles it looks forThe queries array in Search terms and the function field on each job_seekers row.
The job sourcesThe slug list in ATS board list, or copy a branch into Merge sources for a new API.
Who gets matchedRows in job_seekers, each with email, profile, function and min_score.
How strict the ranking isEach seeker's min_score, plus the shortlist limit in Pool shortlist.
The scoring modelThe model in Make scoring batches and the key in Claude score batch.
The digest lookThe HTML template and sender in Email digest.

Replace every YOUR_ placeholder (your Supabase URL and service_role key, your Adzuna app id and key, your model key, and your Resend key), create the three Supabase tables, and add one seeker row to test. If you would rather not wire the auth and database yourself, this is exactly the kind of build you can hire an n8n expert to finish. It pairs well with my match candidates to open roles and my screen job applicants automatically.

Wondering what that costs? See what an n8n expert costs in 2026.

Job matching questions, straight answers.

How do I match and rank jobs with n8n and AI?

I split it into two schedules. An ingest branch runs every 3 hours, crawling free job sources into one shared Supabase pool with no AI so it stays cheap. A daily branch loops each seeker, pulls a shortlist, sends batches to an AI model that scores each job 0 to 100, then emails the top matches.

Why keep the AI out of the ingest step?

Ingest runs every few hours across many boards, so running an AI model there would be slow and cost money for no gain. That branch just crawls, cleans up and saves jobs into a shared pool keyed on id. The scoring branch runs once a day and only ever scores a small pre-filtered shortlist, not the whole pool.

How does the AI score and rank each job?

Make scoring batches builds groups of 20 jobs, each with the candidate profile and a strict answer format. The model returns a score from 0 to 100 plus a fit line and concerns for every job. Attach scores and filter matches those back, keeps anything at or above the seeker's min_score, and sorts high to low.

Which job sources does the workflow pull from?

Five free ones, no scraping. Greenhouse, Lever and Ashby expose open JSON board APIs, so I hit a curated list of company slugs. Adzuna gives a keyed search API across my five search terms, and RemoteOK returns its whole feed. Merge sources combines all three branches before dedup, so adding a source is just another branch.

How does it avoid emailing the same job twice?

Two guards. Upsert to jobs pool matches on id so the pool never holds duplicates. Then, per user, Already-matched ids reads every job_id already saved for that seeker, and Make scoring batches drops those before scoring. Save matches also writes with ignore-duplicates on user and job, so a job is only ever matched once per person.

Can this scale to thousands of job seekers?

That was the design goal. The AI never scores the whole pool, only a per-user shortlist pulled by a cheap SQL pre-filter on the jobs table. For real scale I would pre-rank with embeddings, score only the top few with a small model, cache per user and job, and run n8n in queue mode with a users cursor.

What API keys do I need to run it?

Four, and two sources need none. Supabase holds the pool and seekers with a service_role key. Adzuna gives a free app id and key. An AI provider key powers the scoring. Resend sends the digest and error emails. Greenhouse, Lever, Ashby and RemoteOK are open, so they need no credential at all.

Want this running for your job seekers?

30 minutes, free. Bring your sources and the profiles you want to match. You leave with a plan to turn open job boards into a ranked shortlist in every inbox, whether you build it or we do.

Rather have it built for you?

Skip the build. A free 30-minute call and we set this up in your stack, live in a week or two.