The reason I started Jobzyl was not really the searching. It was the other half: you send a CV, you hear nothing, and you never learn which of the two things went wrong. So the product has to answer a question I find genuinely uncomfortable to answer - does this CV fit this job - and it has to answer it without overclaiming.
It ended up as three separate scoring paths rather than one. That was not indecision. The three differ in what they can see, what they cost, and how far your CV has to travel, and collapsing them into a single number would have hidden exactly the part that matters.
Try it · three scoring paths
How far does the CV travel?
in-browser extraction · no account needed · CV never uploaded
How it works
The CV is parsed in the page. Skills come from a local dictionary unioned with the server's own extraction from the full posting, and the percentage is computed next to you.
What it gives you
A match percentage, the skills you already have, and a ranked list of the ones the posting named that you did not.
What it will not do
When a posting names too few skills to judge, it says so rather than show a number. A posting that named none used to render a confident 0%, which reads identically to a real mismatch.
The keyword path, and the bug that defined the whole feature
The first path runs in the page. The CV is parsed in the browser, skills are extracted from a local dictionary unioned with the server's own extraction from the full posting, and the percentage is computed next to you. Nothing is uploaded, and no account is needed.
It shipped with a bug that I now think of as the design brief for everything else. If a posting named no skills at all - and plenty of postings are three paragraphs of culture and a phone number - the score was a confident 0%.
Zero. Rendered identically to a genuine mismatch. A reader has no way to tell "your CV does not fit this job" from "this posting did not say what it wants", and those are opposite facts.
So the percentage is gated on how many skills the posting actually named. Below the threshold it does not show a number, it says the posting did not name enough to judge. The score got less impressive and much more useful, and that trade is the entire product in one change.
A score that cannot say "I do not know" is not a score. It is a decoration that happens to be numeric.
The semantic path, which is what people mean by matching
Keyword extraction has a hard ceiling: it only matches vocabulary. A CV saying Postgres and a posting asking for PostgreSQL are the same skill and zero shared tokens. Ten years of this problem is why embeddings exist.
So a saved CV is embedded once and stored as a vector, and fit is cosine similarity against job embeddings in pgvector. Same index, one extra use: the up-to-24-row similar-jobs list on a posting is the same query run from the job's vector instead of yours.
Two things I would defend about how narrow this is. It is opt-in, because it structurally requires the CV in the database and I did not want that to be the default that happens to you. And it stays first-party: the vector and the encrypted text sit in Postgres, and the score is computed there. The similarity number is never produced by a third party.
The path that leaves
The third layer is Claude, and it is the only one where your CV leaves the system. It writes cover letters, prepares interview questions, and gives CV feedback - drafted prose, not a score.
Two engineering notes on it. A job description is untrusted text from a third-party board, and it is about to be concatenated with a system prompt and a CV, so every call wraps the posting in delimited blocks under a system guard. Prompt injection via a job ad is not hypothetical for a product whose entire input is scraped text. And the calls sit under a per-user daily quota, because a generous free tier on a metered API is a bill, not a feature.
The transfer is to the US under EU standard contractual clauses. That sentence is on the about page and in the privacy policy, and writing it there was a decision: omitting it would have been easy, technically survivable, and the exact thing I would resent as a user.
Where the CV physically sits
The boring layer, which is the one that would actually hurt someone if I got it wrong.
- CV text is encrypted at rest with Fernet, and the backend refuses to start in production without the key. A missing key should stop a deploy, not quietly store plaintext.
- Multiple CVs per account, and the default flag moves through one atomic RPC rather than two sequential updates. Two updates have a window where either both or neither is default.
- The in-browser copy lives in
sessionStorage, notlocalStorage. A CV should not outlive the tab on a shared machine.
What I am not claiming
No number here says anything about outcomes. I have no measurement of whether a match score gets anyone more interviews, no testimonial, and no logo wall, because none of those has been earned. The site says so in the one place a reader would look for it.
What the three paths are for is smaller and, I think, more defensible: telling you where you stand before you spend an evening on an application, and being explicit about which of them needed your CV to leave the browser to do it.