Paste a job URL. Get a tailored cover letter and packaged application documents in seconds.
Most AI cover letter tools generate the entire letter from scratch — resulting in generic, interchangeable output that reads like it was written by an AI.
This tool works differently. You write the cover letter once, optimised to your voice, experience, and strengths. The AI only fills in the small parts that need to change per application — the company name, what draws you to the role, why this specific organisation. Everything else stays exactly as you wrote it.
The result is a letter that sounds like you, not like ChatGPT, because it mostly is you.
- Scrapes the job posting (title, company, country, responsibilities, qualifications)
- Auto-detects the job country and selects the right resume/cover letter template
- Classifies the role by category (Finance, Marketing, etc.) and picks the matching template
- Fills blanks in your cover letter (
_and[DESCRIPTION]) with company- and role-specific language - Converts to PDF and opens the output folder
Tip: The
PROJECT_MEMORY.mdfile in this repo is a full technical briefing of how the project works. If you get stuck at any point during setup, paste it into Claude, ChatGPT, or any AI assistant and describe your issue — it has everything the AI needs to help you configure, debug, or extend the tool.
python -m venv venv
.\venv\Scripts\activate
pip install -r requirements.txt
playwright install msedgeWindows only:
docx2pdfrequires Microsoft Word to be installed for PDF conversion.
Tip: After setup, you can run the tool without activating the venv each time by using
.\apply.ps1(see Usage below).
PowerShell execution policy: If you get a script blocked error, run this once:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
cp .env.example .envEdit .env and paste your LLM API key.
cp config.example.py config.pyEdit config.py and set all paths:
OUTPUT_BASE— folder where generated applications are savedTEMPLATE_BASE— folder containing your resume/cover letter template subfoldersBUNDLE_APPENDIX— PDFs to append after the cover letter into a combined bundle PDF (leave empty[]if not needed)BUNDLE_NAME— filename for the bundle PDF (e.g."Cover Letter, Recommendations, Transcripts"); defaults to"Cover Letter Bundle"if not setPROFILES— if applying to multiple countries, define one profile per country with its ownOUTPUT_BASEandTEMPLATE_BASE
cp keywords.example.json keywords.jsonEdit keywords.json so each key matches a subfolder name inside your TEMPLATE_BASE. Keywords are matched against the job title (3x weight) and description (1x weight) to pick the right template.
Add a "_broad_categories" key listing any categories that are general fallbacks (e.g. Finance, Accounting). If a broad category wins with no title evidence but a specialist category has description signal, the specialist wins instead. Any category not listed in "_broad_categories" is treated as a specialist.
"_broad_categories": ["Finance", "Accounting", "Investment"]cp locations.example.json locations.jsonEdit locations.json so each key matches a profile name in PROFILES. Values are city/country strings matched against the scraped job location.
Your TEMPLATE_BASE folder should contain one subfolder per job category:
Templates/
├── Finance/
│ ├── Resume.pdf
│ └── Cover Letter.docx
├── Marketing/
│ └── ...
└── ...
Subfolder names must match the keys in keywords.json.
Write your cover letter in full — your background, experience, skills, and voice. Mark only the parts that need to change per application using one of two blank types:
_— for short fills: company name, role title, or a brief phrase[DESCRIPTION]— for guided fills: write what you want Claude to say inside the brackets; it replaces the whole[...]with a natural sentence drawn from the job description
Only sentences containing a blank are sent to the AI. Everything else is untouched.
Template:
I am writing to express my keen interest in the Financial Analyst role at
_.
I applied because [WHAT DRAWS YOU TO THIS COMPANY — their investment mandate, analytical culture, or market position].
Generated:
I am writing to express my keen interest in the Financial Analyst role at RHB.
I applied because RHB's position as one of Southeast Asia's leading financial services groups means rigorous analysis directly informs strategic decisions at scale.
The AI fills the blanks and nothing else. Your sentences stay your sentences.
Page limit: After generating, the tool checks the cover letter PDF page count and warns if it exceeds one page.
Run from a terminal in the ApplyKit folder:
.\apply.ps1Or, if you have the venv active:
python apply.pyPaste the job URL when prompted. The tool auto-detects the country, classifies the role, then asks you to confirm the title, company, and category before generating your application. After each application is saved, it loops back and prompts for the next URL. Type q or press Enter on a blank line to exit.
The browser runs headless (no window appears) during scraping.
Some job sites (e.g. CBRE) block headless browsers. When this happens the tool detects it automatically and prompts:
- Go to the job posting in your browser
- Select all text (
Ctrl+A) and copy (Ctrl+C) - Come back to the terminal and press Enter — do not paste into the terminal
The tool reads your clipboard silently and continues as normal.
Single country — set OUTPUT_BASE and TEMPLATE_BASE directly in config.py. No country prompt appears.
Multiple countries — define PROFILES in config.py and populate locations.json. Country is auto-detected from the job posting; the prompt only appears if detection fails.
# config.py
PROFILES = {
"United States": {
"OUTPUT_BASE": r"C:\...\Applications\US",
"TEMPLATE_BASE": r"C:\...\Templates\US",
},
"United Kingdom": {
"OUTPUT_BASE": r"C:\...\Applications\UK",
"TEMPLATE_BASE": r"C:\...\Templates\UK",
},
}// locations.json
{
"United States": ["united states", "new york", "san francisco", "chicago"],
"United Kingdom": ["united kingdom", "london", "manchester", "edinburgh"]
}| File | What to configure |
|---|---|
config.py |
All folder paths, country profiles, bundle files |
keywords.json |
Job categories and their matching keywords |
locations.json |
Country/city strings mapped to each profile |
Cover Letter.docx |
Your template — write it fully, use _ or [DESCRIPTION] for company/role-specific sentences |
- Python 3.9+
- Windows (for
docx2pdfvia Microsoft Word) - Microsoft Edge (used by Playwright for scraping)
- LLM API key (default: Anthropic Claude — get one here)