An agentic recruitment intelligence platform that parses, scores, and ranks thousands of resumes in minutes — powered by LLM-based document parsing, vector-space semantic matching, and a feedback-driven scoring engine.
Companies that post jobs receive thousands of resumes. Manual screening at that volume is not possible. RecruitIQ solves this with a two-sided platform:
- Applicants browse open positions and submit applications through a clean hosted form — no account needed.
- Recruiters define job requirements once, connect their existing channels (Gmail, Outlook, Greenhouse, Lever), and receive a live-ranked shortlist as applications arrive — without touching a single file manually.
Every resume is parsed by AI into structured data, scored against the job's specific requirements using a 4-dimension weighted engine, stored permanently in a searchable database, and surfaced to the recruiter with a plain-English AI rationale explaining why each candidate scored the way they did.
- Multi-source ingestion — resumes arrive from the platform form, Gmail/Outlook inboxes, and ATS platforms (Greenhouse, Lever) via webhooks. All sources feed the same pipeline.
- AI parsing — Claude API extracts structured candidate data from messy, unformatted resumes (education, experience, skills, certifications, projects).
- 4-dimension scoring — skills match, experience match, education match, and semantic relevance via vector embeddings. Recruiter controls the weight of each dimension.
- Semantic matching — cosine similarity between job description and resume embeddings catches relevance that keyword matching misses.
- Permanent candidate database — every resume ever received is stored, searchable, and filterable across all jobs and all time.
- Email notifications — instant alert when a candidate clears the cutoff, or daily digest summarising all activity. Recruiter can shortlist or reject directly from the email.
- Feedback learning loop — recruiter marks hired/rejected outcomes after each round. Scoring weights self-adjust per company toward patterns that produced good hires.
- AI rationale — every shortlisted candidate gets a 2–3 sentence plain-English explanation of their score. No black-box decisions.
┌─────────────────────────────────────────────────────────────────┐
│ INTAKE LAYER │
│ │
│ Platform Form Gmail/Outlook Greenhouse / Lever │
│ /apply/:jobId OAuth polling Webhook receiver │
│ │ │ │ │
│ └──────────────────┴──────────────────────┘ │
│ │ │
│ Universal Candidate Object │
└──────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────┐
│ INGESTION PIPELINE │
│ │
│ File → MinIO Storage │
│ Job queued → BullMQ + Redis │
│ Claude API → Structured JSON (parse) │
│ Embedding model → Vector stored in Qdrant │
│ Scoring engine → Composite score (0–100) │
│ PostgreSQL → Candidate record written │
│ Score ≥ cutoff → Notification queued │
└──────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────┐
│ OUTPUT LAYER │
│ │
│ Recruiter Dashboard (Next.js) Email Notifications │
│ ├─ Jobs + Shortlist ├─ Instant alert │
│ ├─ Resume Database └─ Daily digest │
│ ├─ Candidate Profile │
│ ├─ Analytics │
│ └─ Integrations │
└─────────────────────────────────────────────────────────────────┘
| Layer | Technology |
|---|---|
| Frontend | Next.js 14 (App Router), Tailwind CSS |
| Backend | Node.js, Express |
| AI — Parsing & Rationale | Claude API (claude-sonnet-4-6) |
| AI — Embeddings | nomic-embed-text (free) or OpenAI text-embedding-3-small |
| Vector Database | Qdrant |
| Job Queue | BullMQ + Redis |
| Primary Database | PostgreSQL 16 |
| File Storage | MinIO (self-hosted S3-compatible) |
| Nodemailer + MJML | |
| OAuth (Gmail, Outlook) | arctic |
| Containerisation | Docker Compose |
recruitiq/
│
├── apps/
│ ├── web/ # Next.js frontend
│ │ ├── app/
│ │ │ ├── (public)/ # Applicant-facing routes
│ │ │ │ ├── jobs/ # Job listings
│ │ │ │ └── apply/[jobId]/ # Application form
│ │ │ ├── recruiter/
│ │ │ │ ├── login/ # Recruiter auth
│ │ │ │ └── dashboard/ # Protected recruiter app
│ │ │ │ ├── jobs/
│ │ │ │ ├── database/
│ │ │ │ ├── analytics/
│ │ │ │ ├── integrations/
│ │ │ │ ├── notifications/
│ │ │ │ └── settings/
│ │ │ └── api/ # Next.js API routes (thin layer)
│ │ └── components/
│ │ ├── ui/ # Base primitives (no component library)
│ │ ├── candidate/ # Profile panel, score bar
│ │ ├── jobs/ # Job cards, new job panel
│ │ └── layout/ # Sidebar, shell
│ │
│ └── api/ # Express backend
│ ├── src/
│ │ ├── routes/
│ │ │ ├── auth.ts # Login, session, logout
│ │ │ ├── jobs.ts # Job CRUD
│ │ │ ├── candidates.ts # Candidate actions
│ │ │ ├── connectors.ts # Connector management
│ │ │ └── webhooks/ # ATS webhook receivers
│ │ │ ├── greenhouse.ts
│ │ │ └── lever.ts
│ │ ├── pipeline/
│ │ │ ├── ingest.ts # Universal ingestion entry point
│ │ │ ├── parse.ts # Claude API resume parsing
│ │ │ ├── embed.ts # Embedding generation
│ │ │ └── score.ts # Composite scoring engine
│ │ ├── connectors/
│ │ │ ├── interface.ts # ConnectorInterface definition
│ │ │ ├── gmail.ts
│ │ │ ├── outlook.ts
│ │ │ ├── greenhouse.ts
│ │ │ └── lever.ts
│ │ ├── queue/
│ │ │ ├── workers.ts # BullMQ workers
│ │ │ └── jobs.ts # Job definitions
│ │ ├── notifications/
│ │ │ ├── email.ts # Nodemailer sender
│ │ │ ├── templates/ # MJML templates
│ │ │ └── scheduler.ts # Digest cron jobs
│ │ ├── db/
│ │ │ ├── client.ts # PostgreSQL pool
│ │ │ ├── migrations/ # SQL migration files
│ │ │ └── queries/ # Typed query functions
│ │ ├── storage/
│ │ │ └── minio.ts # MinIO client + signed URLs
│ │ └── middleware/
│ │ ├── auth.ts # Session validation
│ │ └── rateLimit.ts
│ └── tests/
│
├── docker-compose.yml
├── docker-compose.dev.yml
└── .env.example
- Docker and Docker Compose
- Node.js 20+
- An Anthropic API key (for Claude)
- A Google Cloud project (for Gmail OAuth) — optional for MVP
git clone https://github.com/your-username/recruitiq.git
cd recruitiqcp .env.example .envOpen .env and fill in all required values. See the Environment Variables section below for the full list and what each value does.
docker-compose up -dThis starts PostgreSQL, Redis, MinIO, and Qdrant. The first run pulls all images — allow a few minutes.
cd apps/api
npm install
npm run migrate# In apps/api/
npm run devBackend runs at http://localhost:4000.
# In apps/web/
npm install
npm run devFrontend runs at http://localhost:3000.
The platform does not have public registration — recruiter accounts are provisioned manually via a seed script:
# In apps/api/
npm run seed:recruiter -- --email "recruiter@company.com" --password "yourpassword" --company "Acme Corp"You can now log in at http://localhost:3000/recruiter/login.
Create a .env file at the root. All variables below are required unless marked optional.
# ─── Backend ──────────────────────────────────────────────────
PORT=4000
NODE_ENV=development
# ─── Database ─────────────────────────────────────────────────
DATABASE_URL=postgresql://recruitiq:recruitiq@localhost:5432/recruitiq
# ─── Redis ────────────────────────────────────────────────────
REDIS_URL=redis://localhost:6379
# ─── MinIO (File Storage) ─────────────────────────────────────
MINIO_ENDPOINT=localhost
MINIO_PORT=9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=recruitiq-resumes
MINIO_USE_SSL=false
# ─── Qdrant (Vector DB) ───────────────────────────────────────
QDRANT_URL=http://localhost:6333
QDRANT_COLLECTION=resumes
# ─── Claude API ───────────────────────────────────────────────
ANTHROPIC_API_KEY=sk-ant-...
# ─── Embeddings ───────────────────────────────────────────────
# Options: 'nomic' (free, local) or 'openai'
EMBEDDING_PROVIDER=nomic
OPENAI_API_KEY=sk-... # only needed if EMBEDDING_PROVIDER=openai
# ─── Auth ─────────────────────────────────────────────────────
SESSION_SECRET=a-long-random-string-min-32-chars
SESSION_MAX_AGE_MS=28800000 # 8 hours
# ─── Credential Encryption ────────────────────────────────────
# AES-256 key for encrypting OAuth tokens stored in DB
# Must be exactly 32 bytes (64 hex chars)
ENCRYPTION_KEY=your-64-char-hex-string-here
# ─── Email (Nodemailer) ───────────────────────────────────────
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=noreply@yourcompany.com
SMTP_PASS=your-app-password
# ─── App URL (used in email links) ────────────────────────────
APP_URL=http://localhost:3000
# ─── Gmail Connector (optional) ───────────────────────────────
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://localhost:4000/api/connectors/gmail/callback
# ─── Outlook Connector (optional) ─────────────────────────────
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_REDIRECT_URI=http://localhost:4000/api/connectors/outlook/callback
# ─── Frontend ─────────────────────────────────────────────────
NEXT_PUBLIC_API_URL=http://localhost:4000A complete docker-compose.yml is included that runs every service:
# Start everything
docker-compose up -d
# View logs
docker-compose logs -f api
docker-compose logs -f web
# Stop everything
docker-compose down
# Stop and wipe all data volumes
docker-compose down -vServices and their ports:
| Service | Port |
|---|---|
| Next.js frontend | 3000 |
| Express API | 4000 |
| PostgreSQL | 5432 |
| Redis | 6379 |
| MinIO (API) | 9000 |
| MinIO (Console) | 9001 |
| Qdrant | 6333 |
MinIO console available at http://localhost:9001 (user: minioadmin, pass: minioadmin).
All API routes are prefixed with /api. Protected routes require a valid session cookie from /api/auth/login.
| Method | Route | Description |
|---|---|---|
POST |
/api/auth/login |
Recruiter login. Body: { email, password } |
POST |
/api/auth/logout |
Invalidates session |
GET |
/api/auth/me |
Returns current session user |
| Method | Route | Description |
|---|---|---|
GET |
/api/jobs |
List all jobs for the company |
POST |
/api/jobs |
Create a new job posting |
GET |
/api/jobs/:id |
Get a single job with stats |
PATCH |
/api/jobs/:id |
Update job (title, weights, cutoff, status) |
DELETE |
/api/jobs/:id |
Delete a job and all its candidates |
POST |
/api/jobs/extract |
AI-extract requirements from a raw JD string |
| Method | Route | Description |
|---|---|---|
GET |
/api/candidates |
List all candidates (global DB, with filters) |
GET |
/api/jobs/:jobId/candidates |
Candidates for a specific job |
GET |
/api/candidates/:id |
Full candidate profile |
PATCH |
/api/candidates/:id/status |
Update status: shortlisted / rejected / priority |
PATCH |
/api/candidates/:id/outcome |
Mark hiring outcome: hired / interviewed / rejected_post |
GET |
/api/candidates/:id/resume |
Generate signed URL for resume file download |
| Method | Route | Description |
|---|---|---|
GET |
/api/apply/:jobId |
Get public job info for the application form |
POST |
/api/apply/:jobId |
Submit an application (multipart/form-data) |
| Method | Route | Description |
|---|---|---|
GET |
/api/connectors |
List all connectors for the company |
DELETE |
/api/connectors/:id |
Disconnect and remove a connector |
POST |
/api/connectors/:id/sync |
Manually trigger a poll for email connectors |
GET |
/api/connectors/gmail/auth |
Start Gmail OAuth flow |
GET |
/api/connectors/gmail/callback |
OAuth callback |
GET |
/api/connectors/outlook/auth |
Start Outlook OAuth flow |
GET |
/api/connectors/outlook/callback |
OAuth callback |
| Method | Route | Description |
|---|---|---|
POST |
/api/webhooks/greenhouse |
Receive Greenhouse application webhook |
POST |
/api/webhooks/lever |
Receive Lever application webhook |
| Method | Route | Description |
|---|---|---|
GET |
/api/notifications/settings |
Get notification settings |
PATCH |
/api/notifications/settings |
Update notification settings |
| Method | Route | Description |
|---|---|---|
GET |
/api/analytics/:jobId |
Score distribution, source breakdown, processing stats |
Each resume receives a composite score from 0–100 built from four dimensions:
| Dimension | Method | Default Weight |
|---|---|---|
| Skills match | Set intersection + semantic similarity | 40% |
| Experience match | Duration comparison + role relevance | 30% |
| Education match | Degree tier + field match (rule-based) | 20% |
| Semantic relevance | Cosine similarity (resume vs JD embedding) | 10% |
Weights are recruiter-configurable per job and must total 100%. Disqualifier rules run before scoring — a candidate matching any disqualifier receives score 0 and status disqualified without consuming API credits.
- Go to Integrations in the dashboard and click Connect Greenhouse.
- Enter your Greenhouse API key.
- Copy the displayed webhook URL:
https://yourdomain.com/api/webhooks/greenhouse. - In Greenhouse, go to Configure → Integrations → Web Hooks and paste the URL. Select the
Application CreatedandApplication Updatedevents. - Copy the webhook secret from Greenhouse and paste it into the RecruitIQ connector settings.
Applications submitted on Greenhouse will now appear in RecruitIQ in real-time.
- Go to Integrations and click Connect Lever.
- Enter your Lever API key.
- Copy the webhook URL:
https://yourdomain.com/api/webhooks/lever. - In Lever, go to Settings → Integrations & API → Webhooks and add the URL.
- Paste the signing secret into RecruitIQ.
- Go to Integrations and click Connect Gmail.
- Complete the Google OAuth flow — RecruitIQ requests
gmail.readonlyscope only. - Select which job this inbox should feed into.
- Emails with PDF or DOCX attachments sent to that inbox will be polled every 5–10 minutes and ingested automatically.
After a hiring round, open the candidate profile and mark the final outcome (Hired, Interviewed, Rejected after interview). After enough feedback is collected, the system runs a weight optimisation step:
- It calculates which weight configuration would have ranked the hired candidates higher than they were actually ranked.
- Weights update incrementally per company — nudging the system toward patterns that produced good outcomes.
- All weight changes are logged in
scoring_weights_historywith a before/after snapshot. - Recruiters can view and manually override weights at any time from the Job Settings tab.
This is not LLM fine-tuning — it is a lightweight Bayesian weight update that runs on your own server with no external dependencies.
| Resource | Minimum |
|---|---|
| CPU | 2 vCPUs |
| RAM | 4GB |
| Disk | 20GB (scales with resume volume) |
| OS | Ubuntu 22.04 LTS |
- Install Docker and Docker Compose on your server.
- Clone the repository.
- Copy
.env.example→.envand fill in production values. - Set
NODE_ENV=productionandAPP_URL=https://yourdomain.com. - Use a strong random
SESSION_SECRETandENCRYPTION_KEY. - Point your domain to the server and configure a reverse proxy (Nginx recommended) to forward port 80/443 to the Next.js frontend on 3000.
docker-compose -f docker-compose.yml up -dUse Certbot with Nginx:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com- Create
/apps/api/src/connectors/your-connector.ts. - Implement the
ConnectorInterface:connect(credentials)— store encrypted tokenpoll()— fetch new resumes (email connectors)receive(payload)— handle webhook (ATS connectors)normalize(rawData)— return aIngestedCandidateobject
- Register the connector type in
/apps/api/src/connectors/registry.ts. - Add a UI card in the Integrations dashboard view.
Nothing else changes downstream. The ingestion pipeline only ever consumes the normalised IngestedCandidate object.
# Backend
cd apps/api && npm test
# Frontend
cd apps/web && npm testnpm run lint # in either app
npm run lint:fix # auto-fix- Platform application form (Option 1)
- Gmail connector
- Greenhouse webhook connector
- Lever webhook connector
- Outlook connector
- BambooHR connector
- Workday connector
- Job board integration (LinkedIn, Indeed) — requires partnership
- Multi-language resume support
- Candidate-facing status tracking portal
- Team roles and permissions (admin / viewer)
- Bulk candidate messaging
MIT License. See LICENSE for details.
Built by Spectra — Mechanical Engineering student at Dr. D.Y. Patil Institute of Technology, Pune, transitioning into software engineering and AI systems.
Portfolio · GitHub · LinkedIn