Opt-in feature · Beta
LinkedIn Easy Apply, automated —
with your full review.
Filter, open, fill, screenshot, stop. The bot walks each LinkedIn Easy Apply form in a visible Chromium window so you watch it work — and you flip the "actually submit" switch only when you trust it.
ENABLE_EASY_APPLY=true in your .env.What you get
Filtered to your targets
Date posted, experience level, skip-companies blacklist, required keywords, skip keywords, max years required.
Smart question answering
Pattern-based for the 30 common questions, Gemini AI for the weird ones, cached forever after first answer.
Dry-run by default
Fills the form, screenshots it, stops at the Submit button. You audit. You decide. Then flip the flag.
Hard 10/day cap
Below LinkedIn's detection threshold. Can't be bypassed by a config typo — enforced in code.
Visible browser
Chromium opens on your desktop. You see every click. Stop the bot by closing the window — no hidden background process.
Full audit log
Every attempt logged to
easy_apply_log: applied / skipped / needs_review / failed, with screenshots for failures.
The algorithm, step by step
Every step lives in core/easy_apply.py. Open it in Notepad to verify — it's ~450 lines of commented Python.
- 1
Navigate to a filtered LinkedIn search
We build a URL with your target titles, your primary market's cities, and four LinkedIn filters: f_AL=true (Easy Apply only), f_TPR=… (date posted: 24h / 7d / 30d), f_E=… (experience levels you target), sortBy=DD (newest first). Same query a human would type — just typed for you.
- 2
Lazy-scroll until ~25 cards are loaded
LinkedIn lazy-loads results on scroll. We nudge the left rail 0.9 viewport-heights at a time, 4 times max, with 800ms between scrolls to mimic a human browsing. Never more aggressive than that.
- 3
Deduplicate against your local history
Every job you've already applied to (or already attempted) is stored in your local easy_apply_log. Cards you've seen before are skipped instantly — no double-applications, ever.
- 4
Apply the description filters
Open the right-rail. Drop the job if its description contains any EASY_APPLY_SKIP_KEYWORDS (default: us-citizen, security clearance, senior director), if EASY_APPLY_REQUIRED_KEYWORDS are missing, or if a 'X+ years required' line exceeds your EASY_APPLY_MAX_YEARS.
- 5
Click 'Easy Apply'
Only if a button labeled exactly 'Easy Apply' is visible. We never click any other apply button (we don't auto-fill third-party career sites — too risky).
- 6
Walk the multi-step form
For each visible input on the modal: read its label, canonicalise it (lowercase + strip punctuation), look up the answer in PATTERNS → cache → AI fallback. Type the answer into the right control (text / select / radio / checkbox / textarea).
- 7
Click 'Next' or 'Review' and repeat
LinkedIn typically has 2–5 steps. We keep walking until we either see the 'Submit application' button or hit step 12 (hard ceiling — anything beyond that is probably broken).
- 8
(Dry-run by default) Stop at 'Submit'
On the very first run, EASY_APPLY_DRY_RUN=true. We screenshot the form ready to submit and stop. You audit, then flip the flag once you trust the bot. With dry-run off, we untick 'Follow company' (avoids spam) and click Submit.
- 9
Wait 20–60 seconds. Repeat.
Random jittered delay between every application. The hard cap (EASY_APPLY_DAILY_CAP, default 10) is checked at the top of each iteration — when it hits, the whole run stops and you get a summary in the dashboard.
How question answering works
Four layers — fastest and most deterministic first, AI last. 99% of questions never reach the AI.
Layer 1
Pattern map (deterministic)
30+ regex rules covering 99% of real Easy Apply questions: first/last name, email, phone, years of experience, work authorization, sponsorship, notice period, salary, cover letter, LinkedIn URL, city, country. Lives in core/easy_apply_questions.py — open it in Notepad and you can edit it.
Layer 2
Q&A cache (per-user, per-question)
First time we answer 'Do you have a security clearance?' we store the answer locally in easy_apply_answers. Every subsequent form uses the same answer — consistency, no drift, no AI re-rolls.
Layer 3
Gemini Flash fallback (free tier)
If we hit a question that doesn't match any pattern and your GEMINI_API_KEY is set, we send only the question text + a short profile blurb to Gemini. System prompt forces a short, plain-text answer. No résumé, no .env, no PII beyond what's already on your LinkedIn.
Layer 4
'Needs review' fallback
If both pattern AND AI fail (e.g. AI key missing, or the question is genuinely weird), we screenshot the modal and log the application as needs_review. Your dashboard surfaces it; you finish the application manually in 30 seconds.
Honest risks (read before opting in)
LinkedIn account restriction
Impact: ModerateLinkedIn ToS §8.2 prohibits automation. Their detection is fuzzy — most automation users report no issues at <25 apps/day, but some have been temporarily restricted. Our 10/day cap + 20–60s jitter is well below the detection threshold reported by the community, but it is NEVER zero risk.
Wrong-question answers
Impact: Low (dry-run)When the bot hits a question it has never seen, the AI may guess wrong. The dry-run default means you catch every wrong answer before any application is submitted. Once you've trained the bot for a week of dry-runs, the cache is warm and full-auto is safe.
Over-application
Impact: Low (capped)The hard daily cap (default 10) is checked before every application. It cannot be bypassed by a config typo because it's enforced in db.easy_applies_today(). The .env value can only LOWER the cap, never raise it past 50.
How to enable it
1. Set the flag in .env
ENABLE_EASY_APPLY=true EASY_APPLY_DRY_RUN=true # keep this true for the first week EASY_APPLY_DAILY_CAP=10 LINKEDIN_COOKIE=AQEDAS... # your li_at cookie
2. One-time install (~150MB Chromium)
.venv\Scripts\python.exe -m pip install playwright google-generativeai .venv\Scripts\python.exe -m playwright install chromium
3. Run it (dry-run)
EASY_APPLY.bat # Windows mac/EasyApply.command # Mac
Chromium opens. Watch the bot fill each form. Screenshots land in
data/easyapply_screenshots/.4. When you trust it, flip the switch
# Either: set EASY_APPLY_DRY_RUN=false in .env # Or: run with the --no-dry-run flag .venv\Scripts\python.exe jobybot.py easy-apply --no-dry-run
FAQ
Is this legal?+
Will my account get banned?+
Can I use my Workspace LinkedIn account?+
li_at cookie regardless.Does the bot follow companies?+
What happens if my cookie expires?+
li_at.