Tailor your CV to any job description in seconds. Upload once, apply everywhere — without rewriting a single bullet point.
CV Barber parses your CV into structured data, scores every experience and project entry for relevance against a job description, picks the best ones, writes a tailored summary, and outputs a polished DOCX or PDF. It also generates cover letters, answers application questions, and scores your CV against ATS systems.
🚀 Live app: cv-barber-production.up.railway.app
- CV parsing — upload a PDF or DOCX and get a structured master CV stored in your account
- Smart tailoring — LLM scores each experience and project by relevance (0–10), selects the top N, and writes a job-specific summary
- Download — export as DOCX (original format) or PDF (using built-in themes or custom templates)
- History — all past applications saved and accessible; re-tailor or edit job details at any time
- Reuse your CV — pick any previously uploaded CV without re-uploading
- Cover letter generator — one-click cover letter tailored to job description, downloadable as TXT, DOCX, or PDF
- Application Q&A — paste 1–10 questions from a job application and get tailored answers grounded in your CV and the job description
- Company & job research (optional, requires Tavily API key) — automatically research the company and job posting to enrich your cover letter, Q&A, and tailoring. Gap-aware: if an answer needs company-specific facts, CV Barber searches for them automatically.
- ATS scoring — general CV quality score and job-match score with matched/missing keywords and improvement suggestions
- JD enrichment (optional, requires Tavily API key) — auto-supplement thin job descriptions with research data to improve scoring accuracy
- Section editor — toggle individual CV sections and entries on/off; preview updates instantly
- In-browser preview — live HTML preview of your tailored CV before downloading
- Custom templates — upload your own HTML or LaTeX templates (≤256 KB). CV Barber auto-converts filled-in CVs into reusable templates with Jinja placeholders, or uses templates as-is if placeholders already exist. All custom templates are sandboxed for security.
- Built-in themes — 5 ATS-optimized PDF themes (classic, modern, compact, professional, minimal)
- Email verification — 6-digit code sent to your email (uses Brevo)
- Google OAuth login — sign in with Google and link to your email account
- Feedback — submit bugs, feature requests, or general feedback directly from the app
- Admin dashboard (superuser-only) — view metrics (users, uploads, applications, template popularity), review feedback, and monitor platform activity
| Layer | Technology |
|---|---|
| Backend | FastAPI + Uvicorn |
| Primary LLM | Groq (openai/gpt-oss-120b) |
| Fallback LLM | Google Gemini (gemini-2.5-flash) |
| PDF extraction | PyMuPDF (fitz) with optional OCR (Tesseract) |
| DOCX extraction | python-docx |
| Output generation | python-docx (DOCX), WeasyPrint (HTML→PDF), Tectonic (LaTeX→PDF) |
| Structured output | Pydantic v2 |
| Database | PostgreSQL (async SQLAlchemy + asyncpg) |
| Migrations | Alembic |
| Auth | FastAPI Users (JWT bearer) |
| Frontend | React + Vite + Tailwind v4 |
| State management | Zustand |
| Routing | React Router |
| Deployment | Docker Compose + Railway |
Upload CV → extract text (PyMuPDF / python-docx / OCR)
→ LLM parse → MasterCV stored in Postgres (JSONB)
→ background: general ATS score
Tailor → load MasterCV → LLM score each entry (0–10) against JD
→ top N entries selected → TailoredCV saved as Application
→ background: job-match ATS score
Download/Preview → load Application → apply section_config (toggle state)
→ render DOCX (python-docx) or PDF (Jinja2 + WeasyPrint)
LLM providers: Groq is primary (openai/gpt-oss-120b). If Gemini keys are set, exhausted/rate-limited Groq requests fall back to Gemini automatically. Keys are round-robin rotated; daily quota exhaustion is detected and the key is blocked until midnight Pacific.
Data model (simplified):
MasterCV — full_name, email, education[], experience[], projects[], skills[], certifications[]
Application — master_cv_id + job_title/company/JD + TailoredCV (JSONB) + section_config (JSONB)
Auth: JWT bearer (1-hour access token) + refresh token. All /api/* routes require Authorization: Bearer <token>. The frontend auto-refreshes on 401 and retries the original request; redirects to login if refresh fails.
Rate limits: LLM endpoints → 30 req/hour / 80 req/day per user. Auth endpoints → 5 attempts/60s per IP.
Deduplication: SHA-256 of the parsed CV text is stored; re-uploading the same CV returns the existing master CV immediately without re-parsing.
The quickest way to run the entire app with a real Postgres database.
Prerequisites: Docker Desktop
git clone https://github.com/MoaazEmam/CV-Barber.git
cd CV-Barber
cp .env.example .env # then fill in your keys (see Environment variables below)
docker compose up -d --buildOpen http://localhost:8000. Migrations run automatically on container start.
docker compose logs -f app # stream logs
docker compose down # stop (data is preserved in the postgres_data volume)
docker compose down -v # stop AND wipe the databasePrerequisites: Python 3.12+, Poetry, Node 18+, Docker (for Postgres only)
# 1. Clone and install backend dependencies
git clone https://github.com/MoaazEmam/CV-Barber.git
cd CV-Barber
poetry install
# 2. Copy and fill in environment variables
cp .env.example .env
# 3. Start only the database
docker compose up -d postgres
# 4. Apply migrations and start the backend
alembic upgrade head
uvicorn app.api.main:app --reload --port 8000
# 5. In a separate terminal, start the frontend dev server
cd frontend
npm install
npm run dev # http://localhost:5173 — proxies /api, /auth, /users to :8000After any frontend change in production mode:
cd frontend && npm run build # writes bundle to app/static/
docker compose restart app| Variable | Default | Required | Description |
|---|---|---|---|
DATABASE_URL |
— | yes | Async Postgres URL: postgresql+asyncpg://user:pass@host:port/db |
SECRET_KEY |
— | yes | JWT signing key — generate with openssl rand -hex 32 |
LLM_PROVIDER |
groq |
no | groq, gemini, or ollama |
GROQ_API_KEYS |
— | no | Comma-separated Groq keys; ~14,400 req/day per key. Single key also accepted as GROQ_API_KEY |
GROQ_MODEL |
openai/gpt-oss-120b |
no | Groq model name |
GEMINI_API_KEYS |
— | no | Comma-separated Gemini keys; auto-fallback when Groq is exhausted. Single key also accepted as GEMINI_API_KEY |
GEMINI_MODEL |
gemini-2.5-flash |
no | Gemini model name |
OLLAMA_BASE_URL |
http://localhost:11434 |
no | Ollama server URL |
OLLAMA_MODEL |
llama3.1 |
no | Ollama model name |
CEREBRAS_API_KEYS / CEREBRAS_MODEL |
— / gpt-oss-120b |
no | Optional fallback provider (OpenAI-compatible) |
NVIDIA_API_KEYS / NVIDIA_MODEL |
— / nvidia/llama-3.3-nemotron-super-49b-v1 |
no | Optional fallback provider |
MISTRAL_API_KEYS / MISTRAL_MODEL |
— / mistral-large-latest |
no | Optional fallback provider |
OPENROUTER_API_KEYS / OPENROUTER_MODEL |
— / meta-llama/llama-3.3-70b-instruct:free |
no | Optional fallback provider |
LLM7_ENABLED |
false |
no | Enable LLM7 (keyless provider; set to true to include in chain) |
LLM7_API_KEYS / LLM7_MODEL |
— / deepseek-r1-0528 |
no | Optional fallback provider |
ZAI_API_KEYS / ZAI_MODEL |
— / glm-4.5-flash |
no | Optional fallback provider |
LLM_INTERACTIVE_CHAIN / LLM_BACKGROUND_CHAIN |
— | no | Comma-separated provider order override (e.g. groq,gemini,cerebras) |
TOP_N_EXPERIENCE |
3 |
no | Max experience entries kept after scoring |
TOP_N_PROJECTS |
3 |
no | Max project entries kept after scoring |
OCR_ENABLED |
true |
no | OCR fallback for scanned PDFs — requires Tesseract (pre-installed in Docker) |
OCR_DPI |
300 |
no | DPI for OCR rendering |
ALLOW_DOCX_TO_PDF |
true |
no | Allow DOCX inputs to render as PDF; if false, DOCX can only render back to DOCX |
TAVILY_API_KEY |
— | no | Web search API key for company research and JD enrichment; features disabled when unset |
BREVO_API_KEY |
— | no | Brevo REST API key for email verification. Unset in dev = codes logged to console instead |
MAIL_FROM |
no-reply@example.com |
no | From address for verification emails |
MAIL_FROM_NAME |
CV Barber |
no | From name for verification emails |
GOOGLE_OAUTH_CLIENT_ID / GOOGLE_OAUTH_CLIENT_SECRET |
— | no | Google OAuth credentials; Google login disabled when unset (email/password auth always available) |
APP_BASE_URL |
http://localhost:8000 |
no | Public origin of the app; used for OAuth redirects and SPA handoff |
ALLOWED_HOSTS |
* |
no | Comma-separated allowed hosts; * disables host check |
ENV |
development |
no | development (pretty logs, SQL echo) or production (JSON logs) |
API_HOST |
0.0.0.0 |
no | Uvicorn bind host |
API_PORT |
8000 |
no | Uvicorn bind port |
Company & job research (cover letter, Q&A enrichment, JD auto-supplement):
- Requires:
TAVILY_API_KEY(get one free at tavily.com) - When disabled: Q&A, cover letters, and ATS scoring still work, but without company-specific research
- When enabled: The app auto-enriches your answers with company facts and auto-supplements thin job descriptions
Email verification (account security):
- Requires:
BREVO_API_KEY(get one free at brevo.com) - When disabled: verification codes are logged to the server console instead of emailed
- When enabled: users must verify their email address within 15 minutes of signup
Google OAuth (faster login):
- Requires:
GOOGLE_OAUTH_CLIENT_IDandGOOGLE_OAUTH_CLIENT_SECRET(get them at Google Cloud Console) - When disabled: only email/password login is available
- When enabled: users can sign in with one click via their Google account
The easiest free configuration uses Groq as primary with Gemini as fallback:
# Core (required)
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/cvbarber
SECRET_KEY=$(openssl rand -hex 32)
# LLM providers (at least one required)
GROQ_API_KEYS=your_groq_key_1,your_groq_key_2
GEMINI_API_KEY=your_gemini_key # optional fallback
# Optional but recommended
TAVILY_API_KEY=your_tavily_key # company research
BREVO_API_KEY=your_brevo_key # email verification
GOOGLE_OAUTH_CLIENT_ID=your_oauth_client_id # Google login- Groq: ~14,400 requests/day per free key. Get at console.groq.com
- Gemini: Auto-fallback when Groq is exhausted. Get at aistudio.google.com
- Tavily: Free plan includes daily searches. Get at tavily.com
- Brevo: Free tier covers verification emails. Get at brevo.com
| Method | Endpoint | Description |
|---|---|---|
POST |
/auth/register |
Register a new account (JSON) |
POST |
/auth/jwt/login |
Login → returns access_token (form-urlencoded) |
GET |
/users/me |
Get current user profile |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/parse |
Upload PDF/DOCX → parse and store master CV |
GET |
/api/master-cvs |
List all uploaded master CVs |
POST |
/api/tailor |
Tailor master CV to a job description |
GET |
/api/download/{id}?format=docx|pdf |
Download tailored CV |
GET |
/api/preview/{id} |
HTML preview of tailored CV |
GET |
/api/history |
List all tailored applications |
GET |
/api/applications/{id} |
Full application detail |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/applications/{id}/structure |
Section tree for a tailored CV (use this in the editor) |
PATCH |
/api/applications/{id}/sections |
Save section toggle state |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/templates |
Upload a custom .html or .tex template (≤256 KB); auto-converts if no placeholders |
GET |
/api/templates |
List all user's uploaded templates |
DELETE |
/api/templates/{id} |
Delete a custom template |
GET |
/api/applications/{id}/template-options |
Show available templates for this application + selected one |
PATCH |
/api/applications/{id}/template |
Select a template for this application |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/cv/{id}/ats/general |
General CV quality score |
POST |
/api/applications/{id}/ats/job |
Job-match ATS score |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/applications/{id}/qa |
Get tailored answers to application questions |
GET |
/api/applications/{id}/qa |
Fetch previously answered questions |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/applications/{id}/cover-letter |
Generate and store a cover letter |
GET |
/api/applications/{id}/cover-letter/download?format=txt|docx|pdf |
Download cover letter |
Interactive API docs at /docs (FastAPI Swagger UI).
app/
├── api/
│ ├── main.py # FastAPI app factory, middleware, SPA static serving
│ ├── models.py # Request/response Pydantic models
│ ├── dependencies.py # Async DB CRUD functions
│ ├── render_helpers.py # Render dispatch logic
│ ├── rate_limit.py # SlowAPI rate limiting (LLM endpoints + auth)
│ └── routes/ # parse, tailor, preview, templates, history, structure,
│ # qa, ats, master_cvs, cover_letter, auth_refresh, verification
├── auth/ # FastAPI Users config, manager, schemas, validation
├── db/ # SQLAlchemy models, engine, async CRUD
├── schemas/ # Domain models: BaseCV, MasterCV, TailoredCV, TailoringConfig, etc.
├── extraction/ # PDF (PyMuPDF+OCR), DOCX, two-column detection
├── services/ # Email (Brevo), email verification logic
├── llm/ # LLM clients (Groq, Gemini, Ollama, OpenAI-compatible), prompts,
│ # parser, scorer, qa, ats_scorer, cover_letter, key rotation
├── pipeline/ # Parse & structure pipeline, schema extraction, dedup
├── generation/ # DOCX + PDF rendering, template registry, section filter
├── static/ # Built React Vite bundle (served as SPA with 404 → index.html)
└── config.py # Settings via pydantic-settings
frontend/ # React + Vite + Tailwind v4
alembic/ # Async-mode Alembic migrations
tests/ # pytest suite (async SQLite in-memory)
OCR note: scanned/image-only PDFs fall back to Tesseract. The Docker image installs
tesseract-ocrautomatically. On a local dev machine without Tesseract, OCR is skipped gracefully and the parse returns a "looks scanned" warning.
poetry run pytest tests/ -vTests use an in-memory SQLite database — no Postgres required.
The app is deployed on Railway using the included docker-compose.yml.
To deploy your own instance:
- Fork this repo
- Create a new project on Railway → Deploy from GitHub
- Add a Postgres plugin to the project
- Set all required environment variables (
DATABASE_URL,SECRET_KEY, LLM keys) - Railway builds the Docker image, runs migrations, and gives you a public URL
