Chapply Docs

How Chapply actually works

Not a marketing page — this is the real mechanism behind matching and autofill: what gets extracted, what gets sent where, what's a hard rule versus a soft signal, and the exact limits involved. If something here changes, this page changes with it.

Overview

Chapply does two things: it matches you to jobs using your resume and stated preferences, and it fills out the application forms it finds using the same profile. Both halves are built around one rule that shows up everywhere in this document: never invent. If your resume doesn't say it, Chapply doesn't claim it — not in a match rationale, not in an autofilled field, not in what gets extracted in the first place.

Everything below reflects what's actually deployed right now, not a roadmap.

Resume parsing

Your resume goes through Claude once per upload, split into two outputs.

When you upload a PDF, DOCX, or plain text resume, the file is converted to plain text (no macros or embedded scripts are ever executed — see Security) and sent to Claude with instructions to extract, not interpret. The result is two objects:

1. Your profile (resume_json)

Structured, editable data used for both autofill and matching:

name / email / phone address work history education certifications skills links

Every one of these is editable afterward in Settings — Personal Info, Links, Work Experience, and Education each have their own tab. Nothing Claude extracts is locked in as "final."

2. Your search profile (search_config)

Job-search preferences, seeded from your resume where it's explicit, but meant to be tuned by hand: target roles, career-change roles, keywords, level, locations, remote scope, compensation floor, eligibility toggles (contract roles, FedRAMP, citizenship, clearance), and a security-clearance field.

search_config also carries two AI-generated fields you don't edit directly — strong_fit_signals and deprioritize — covered in The matching pipeline below.

Accuracy rule

The parsing prompt is explicit: never invent or upgrade a claim beyond what the resume states. "Familiar with X" is not "expert in X." A title, date, or credential that isn't clearly stated gets omitted, not guessed. The one deliberate exception is the four willingness/eligibility toggles (contract roles, FedRAMP, citizenship, clearance) — those default to permissive since a resume rarely states them either way, and you refine them yourself in Settings.

Filters vs. evidence vs. interest

Three different things feed matching, and they're not interchangeable.

Hard filters — search_config

Roles, Location, Compensation, and Eligibility in Settings are pass/fail gates. A posting that fails any of them is never shown as a target match, full stop — it either doesn't qualify at all or gets filed as a labeled near-miss (see below). These are seeded from your resume at upload but are entirely yours to edit.

Confirmed experience — your resume itself

Skills, work history, certifications, and education are treated as things you've actually done. This is what a match rationale is allowed to describe as your experience ("your work building X," "your background in Y").

Interest, not proof — keywords

The one field that's neither of the above. Keywords are topics you want to see roles about — not evidence you've worked with them. Matching is instructed to describe a keyword match as "aligns with your interest in X," never "your X expertise." This distinction is enforced directly in the prompt that writes each card's rationale, specifically to stop a stated interest from getting silently upgraded into a false claim of experience.

The matching pipeline

Two model calls per run: one to screen, one to write.

Step 1 — Triage

Every fetched posting gets sorted into exactly one bucket by a fast model, run against your hard filters:

BucketMeaning
targetA strong match against your stated roles, level, and keywords.
career_changeMatches your career-change roles if you listed any; otherwise the model's own judgment on an adjacent-skill fit it can name specifically.
near_missClose, but excluded — always with one specific reason (table below).
(dropped)Not relevant at all — omitted entirely, not shown anywhere.

Near-miss reasons

comp_below_floorUnder your stated minimum compensation.
missing_hard_certRequires a certification you don't hold and the posting treats as mandatory.
location_stretchOutside your preferred locations / remote scope.
seniority_mismatchLevel doesn't line up with what you're targeting.
requires_clearanceRequires a security clearance and your eligibility toggle says no.
requires_citizenshipRequires citizenship/permanent residency and your toggle says no.
requires_fedrampCenters on FedRAMP work and your toggle says no.
contract_not_wantedContract/contractor role and your toggle says no.

A location or clearance requirement is a hard constraint regardless of how well everything else fits — a 95%-fit posting outside your location is a near-miss, not a target, unless you've explicitly widened that filter.

Step 2 — Compose

Every posting that made it past triage gets a rationale written by a stronger model, using both search_config's condensed signals and the fuller resume context — citing the actual employer, tool, or metric behind the match rather than restating a generic signal. Salary display follows one rule: if the posting has an employer-posted number, show it as-is; otherwise, generate an estimate and label it clearly as an AI estimate, never presented as if the employer posted it.

strong_fit_signals & deprioritize

At parse time, Claude also writes a distilled summary of what your resume proves — specific tools with evidence of real use, quantified achievements, domain experience, certifications, career trajectory — and, separately, signals about what you're stated to be moving away from. This rides along in every triage and compose call alongside the fuller resume context. It's visible (read-only) at the bottom of the Job Preferences tab in Settings, and only refreshes on a resume re-upload — there's no separate hand-edit path for this specific summary, though editing the underlying Skills/Work History/Education does feed the next parse.

Resume context & caps

What actually rides along on every matching run, and why it's capped.

Beyond the condensed strong_fit_signals summary, every triage and compose call also receives a structured slice of your resume directly — skills, certifications, computed years of experience, and work history with real bullets — so the model can cite specifics instead of only ever repeating a parse-time summary. This is capped, for one reason: an uncapped, unbounded resume (or a deliberately padded one) would grow token cost on every single run for the life of a subscription, not just once.

WhatCapWhy this number
Work history entries12Measured against a real, denser-than-typical resume (9 roles including two internships) — 12 leaves headroom above that with room to spare.
Bullets per entry10Same measurement: captured 100% of every real bullet on that resume, including a 9-bullet current role.
Skills60Purely defensive — real measured counts run well under this; it exists to bound a keyword-stuffed outlier, not to trim a normal resume.
Certifications25Same reasoning as skills.

These aren't guessed round numbers — they were set by actually measuring token counts (via Anthropic's token-counting API) against a real resume at several cap levels before picking one. At these caps, a typical resume is sent in full; the caps only start trimming content for someone with an unusually long work history or an unusually padded skills list, and even then, the parse-time strong_fit_signals summary (generated from your full, uncapped resume) still carries distilled signal from whatever gets trimmed out of the structured version.

Cost, for scale

The gap between these caps and sending a resume completely uncapped is on the order of 50-300 tokens per run — at combined model rates, a fraction of a cent. The caps exist to bound rare outliers, not because a normal resume is expensive to send in full.

The no-padding policy

A thin week stays thin — on purpose.

If a run only turns up three real matches instead of five, you get three cards, not three good ones plus two stretched to fill a number. The results panel says so explicitly ("N new strong match(es) this run — fewer than usual") rather than silently padding the list with weaker fits to hit a target count. This is treated as a product principle, not an edge case to patch around.

When autofill appears

Two ways it can trigger — one automatic, one explicit.

Automatic: the on-page button

On Greenhouse and Lever, the button shows on any page on those domains — those platforms have dedicated adapters and are treated as always-relevant. Everywhere else, it only appears if the page has either at least 3 confidently-recognized form fields or at least one file upload input — a deliberate threshold meant to avoid showing up on pages that aren't actually job applications.

Known, tested limits

This heuristic isn't perfect in either direction, confirmed by direct testing: a generic contact form with name/email/phone fields can trigger it (a false positive — low cost, you just see an unwanted button), and a real application with unusually-labeled fields and no file upload can fail to trigger it at all (a false negative). The second case is the more consequential one, which is exactly why the manual option below exists.

Explicit: "Autofill this tab" in the panel

The side panel has its own Autofill button that acts on whichever tab is currently active, regardless of what the on-page heuristic decided. Looking at a matched job's rationale in the panel and clicking Autofill is the "yes, this is an application" signal — no guessing involved. This is the reliable fallback for any page the automatic detection misses.

How fields get matched

Dedicated handling for two platforms, a generic matcher for everywhere else.

Greenhouse and Lever get precise, hand-tuned selectors for their specific form structures, including their combobox-style Education sections. Every other site falls through to a generic matcher: form fields are scored against a synonym dictionary (label text, placeholder, name/id attributes, ARIA labels) and filled if the match confidence clears a threshold.

Bridging different wording for the same question

Two sites asking the identical question can use completely different words for the answer — "Man"/"Woman" versus "Male"/"Female," or "I am not a protected veteran" versus a company's own "No, I am not a veteran or active member." Plain text matching can't bridge that; there's often zero shared vocabulary. Autofill uses semantic classifiers for exactly this — gender, veteran status, Hispanic/Latino phrasing, and yes/no/decline polarity are recognized by meaning, not just by shared words, each confirmed against a real mismatch found during testing.

Work authorization & EEO fields

Set once by hand — never guessed from a resume.

A resume can't state whether you need visa sponsorship or how you'd answer a voluntary demographic survey. These are set once in Settings and reused everywhere:

Work Authorization

Five yes/no questions — authorized to work, requires sponsorship, 18 or older, willing to relocate, open to remote — answer nearly every screening question a serious application asks.

EEO / Demographic

Entirely optional. Veteran status, disability status, gender identity, racial/ethnic background, sexual orientation, and transgender identity all use the same standard self-identification wording real forms use (VEVRAA/Section 503 language, common DEI-survey phrasing) rather than free text, since matching exact wording against a real form's options is far more reliable that way. Every option list includes an explicit decline-to-answer choice, and leaving a field blank just means that question goes unanswered on forms — same as any other blank field.

Mark all vs. pick one

Gender, race, and sexual orientation support "mark all that apply." Each has an explicit toggle: mark every checked option (default) versus pick just one, in the order checked — because a site that happens to support multiple selections would otherwise mark every stored value even when you only meant one as a fallback in case your first choice isn't offered. That distinction is a statement about your own data, not something a form's markup can answer on its own, so it isn't inferred — it's a setting you control.

Required-field badges

Fields known to be required on specific platforms show a small Required badge with a tooltip naming the site — sourced from a hand-curated, evidence-only list (confirmed against real form markup, not guessed), so you see what's likely to matter before ever running autofill anywhere.

What autofill won't do

  • It never submits a form. Every field gets highlighted after filling specifically so you can review before you click submit yourself.
  • It never invents a value. A field with nothing in your profile to match is left blank and listed as needing review, not guessed at.
  • It only runs where you are. Autofill acts on the page you're on, or the tab you explicitly pointed it at from the panel — never in the background, never on a schedule.

Security

What happens with an uploaded file or a maliciously-crafted field value.

File uploads

PDF and DOCX parsing extracts text only — nothing in the pipeline ever renders or executes the document, so embedded PDF actions or Office macros are never run. Uploads are capped at 10MB, and unsupported file types are rejected outright.

Rendering

Every field in the extension — including AI-written rationale text and anything you type — renders through methods that never parse or execute HTML, regardless of what string is inside it. Autofill fills third-party forms the same way: through native form-value APIs, not by injecting markup, so a deliberately malicious string in a profile can't execute on the job site you're applying to either.

Storage

All database access goes through parameterized queries — no raw SQL built from user input, anywhere. Row-level security restricts every table to the authenticated owner's own rows.

What isn't a 100% guarantee

Prompt injection into Claude — a resume crafted to try to manipulate how it's parsed or matched — can't be mathematically ruled out for any LLM-integrated product. What bounds it here: output is always constrained to strict JSON, parsing is explicitly instructed never to invent, and there is no path from a Claude response to code execution anywhere in the pipeline. Worst realistic case is a strange-but-still-valid profile, not a compromised system.

Where your data goes

ServiceWhat it getsWhy
Anthropic (Claude)Resume text, search preferences, job postingsResume parsing, job matching, rationale writing
SupabaseYour full profile and accountDatabase + authentication, encrypted at rest, row-level security
StripeBilling only — never your resume or profilePayment processing; Chapply never sees your card number
JSearch, Greenhouse, LeverNothing of yours — queried one-wayJob posting sources; your resume/preferences are never sent to them

Full detail, retention periods, and how to delete everything: Privacy Policy.

Limits & caps reference

LimitValue
Resume re-uploads1 per month
Job postings lookback window4-7 days, adaptive to how recently you last ran
Near-misses shown per runUp to 4
"Seen" postings suppressed for90 days (won't repeat a posting you've already been shown)
Job posting cache freshness24 hours
Resume work history sent to matchingUp to 12 entries, 10 bullets each
Skills / certifications sent to matchingUp to 60 / 25
API request size1MB (10MB for the resume file upload itself)

Billing

One tier, currently.

Basic
Price$5/month or $50/year
Run scheduleTuesday and Friday
Matches per run5 target + 5 career-change
CancellationAny time, from the extension — takes effect immediately

Deleting your account (Settings → Account → Delete my account) cancels any active subscription first, then permanently removes your profile, resume, and match history in one step. No waiting period.

FAQ

Why didn't a specific field autofill?

Either there's nothing in your profile that maps to it (check Settings), or the page's own wording didn't clear the matching confidence threshold. Skipped fields are always listed in the result summary with a reason, not silently left blank.

Why does a match cite a "keyword" as a skill I want, not one I have?

Because that's what a keyword means in Chapply's model — interest, not proof. If it should be described as real experience, it belongs in your resume's Skills or Work Experience instead, not Job Preferences' Keywords field.

Can I see exactly what got sent to the matching model?

Not yet as a UI feature — this page documents the mechanism precisely, but there's no in-product "show me the raw request" view today.

Does autofill ever run without me clicking something?

No. Every fill is triggered by either the on-page button or the panel's Autofill button — both explicit clicks.

Can I upload a different resume for different types of jobs?

You don't need to — one resume already covers this. target_roles and career_change_roles in Job Preferences are separate lists, and every card's rationale is written per posting, citing whichever real experience on your resume actually applies to that specific job. So searching two adjacent paths off one background — say, a primary target role plus an adjacent one you'd consider — is already what those two lists are for, without needing a second resume artifact.

Community

Discord — coming soon.

A community space for questions about exactly what's on this page is planned. Check back here for the invite once it's live.