Enterprise-grade, multi-agent hiring intelligence — automated resume screening, blind evaluations, real-time analytics, and 1:1 Web ↔ Mobile parity with offline resilience. Production SEO + ASO ready for 100k+ organic reach.
Live Platform • Live Demo Video • Presentation Deck • Report Bug • Request Feature
- Executive Overview
- Problem → Solution → Impact
- Performance Benchmarks & Engineering Metrics
- Visual Architecture & Pipeline Diagrams
- Core Feature Suite
- Multi-Agent LangGraph Architecture
- SEO & ASO — Production Organic Growth Infrastructure
- Cross-Platform Mobile Ecosystem (Expo 57)
- Web ↔ Mobile Parity — Launch Readiness
- Privacy, Ethics & Regulatory Compliance (GDPR & NYC LL144)
- Technology Stack
- Database Schema & Vector Search (pgvector)
- Directory Structure
- Installation & Setup
- Offline-First Development & LocalAuth
- API Reference
- Testing & Quality Gates
- Presentation & Demo Links
RecruitAI replaces manual screening with a deterministic, multi-agent AI system built for enterprise scale and 1:1 Web ↔ Mobile parity:
- Bias Elimination via Blind Mode: Redacts names, contacts, demographics before LLM evaluation — skill-only scoring with per-campaign toggle persisted via
SecureStore/localStorageand synced toPATCH /api/users/me. - Supervisor-Worker Orchestration: LangGraph coordinates 5 agents — JD ingestion, vector screening, interview probe generation, salary benchmarking, scheduling — with explicit HITL gates before any email/calendar dispatch.
- RAG with pgvector: Google Gemini Embedding 2 (
384-dMRL) + IVFFlat cosine retrieval at 31ms p95, query expansion, semantic relevance scoring — not keyword filters. - Full Parity: Every recruiter workflow (ingest file + text, rubric calibrate, copilot
@mention+ 20 presets, triage with offline queue, compare, schedule, outreach via real SMTP, ATS export JSON/CSV, PDF dossier, analytics) is identical on Next.js 16 and Expo 57. Verified. - Executive Dossiers: Two-pass ReportLab
NumberedCanvasPDF withPage X of Y, vector styling, EEOC/GDPR disclaimers. - HITL Safeguards: Outbound emails (
POST /api/email/send), status overrides (POST /api/candidates/evaluate), calendar locks — all gated; cross-device status now unified onPOST /candidates/evaluate(tech 4/5 + comm 3/5).
| Problem | Engineering Solution | Quantifiable Impact |
|---|---|---|
| 15–20h per req skimming 200 PDFs, keyword ATS rejects qualified talent | Semantic embeddings 384-d + rubric scoring + 500-doc parallel ingestion 1.38s/doc |
10× faster screening; rank 500 resumes in minutes |
| Unconscious bias (names, photos, prestige) | Blind Mode pre-LLM redaction + audit-logged impact_ratio for NYC LL144 |
100% blind compliance, p20/p80 audit API |
| Inconsistent interviewer variance ±30% | 5-pillar calibrated rubric with editable ±5% calibrator (Web Jobs + Mobile RequisitionSpec) — POST /api/chat Calibrate re-scores |
Consistent green ≥80% / amber 50-79% / rose <50% matrix |
| Generic mass-blast emails (<5% reply) | Role + skill-grounded drafting + HITL confirm — Web OutreachPage:161 now POST /api/email/send (real SMTP, fallback simulation), Mobile EmailDrafter:126 same |
Higher reply, zero autonomous sends |
| ATS re-entry | Validated POST /api/export/ats JSON/CSV (convertAtsToCsv quotes) — Web AtsExportModal + Mobile ats-export.tsx parity, Greenhouse/Lever/Workday schema |
1-click handoff, no glue code |
| Cross-device drift | Unified POST /candidates/evaluate (tech/comm) + offline mutation queue (localStorage/SecureStore replay on health/focus) |
100% loss-free drain, 4s/12s poll sync |
| No organic discoverability | 26-route sitemap + robots LLM allowlist + 4-type JSON-LD + sitelinks nav + 20 SEO pages (features/solutions/guides/compare) + Play ASO 30/80/4000 chars | 100k/mo organic target — crawlable without auth |
| Metric / Operation | Benchmark | Methodology |
|---|---|---|
| Resume Ingestion & Parsing | 1.38s / doc | PDF/DOCX extraction, cleaning, semantic chunking |
| Vector Embedding | 185ms / candidate | Gemini Embedding 2 (384-d MRL) |
Vector Retrieval (pgvector) |
31ms p95 48ms | Cosine similarity, IVFFlat, resume_chunks |
| Multi-Agent Turn | 1.72s median | Supervisor routing + worker + Pydantic validation |
| PDF Dossier | 395ms / 4-page | ReportLab two-pass, vector headers |
| Mobile Cold-Start Hydration | <115ms | expo-secure-store non-blocking restore |
| LLM Failover | <280ms | Circuit-breaker Gemini 2.5 Flash → 3.1 Flash Lite |
| Offline Drain | 100% loss-free | Dedup by candidateId queue replay on reconnect |
| Web Build | 6.1s, 46 static routes | Next.js 16 Turbopack, avif/webp 1yr immutable, optimizePackageImports |
| Sitemap Generation | 26 URLs, daily→monthly | sitemap.ts priorities 1.0→0.5, excludes /dashboard//api |
| API Retry | 45-60s timeout, 2× backoff, 401 refresh | Web apiClient:17 + Mobile apiClient:47 |
================================ QUALITY GATE METRICS ================================
Backend Test Suite (Pytest) : 159 tests collected & passing (100% pass rate)
Mobile Test Suite (Jest) : 38 unit & integration tests passing (9/9 suites)
Frontend Next.js Build : 0 TS errors (strict), 46 routes prerendered
Mobile TypeScript Suite : 0 TS errors (strict)
Backend Type Checker : 0 errors across 71 source files (Mypy strict)
Backend Linter : 100% compliance (Ruff check app/)
Overall Coverage : 197+ Automated Verification Tests Passing
======================================================================================
| Signal | Target | Implementation |
|---|---|---|
| LCP / INP / CLS | <2.5s / <200ms / <0.1 | next/font display:swap, next/image avif, preconnect fonts.googleapis, Cache-Control immutable next.config.ts:1 |
| Sitemap | https://recruitaiofficial.vercel.app/sitemap.xml |
26 URLs, changeFrequency daily→monthly |
| Robots | …/robots.txt |
Allow: /features/*, /solutions/*, /guides/*, /compare/* + LLM bots GPTBot, ClaudeBot… |
| Structured Data | 4 types | SoftwareApplication (rating 4.9/127) + Organization + WebSite SearchAction + FAQPage + ItemList sitelinks + BreadcrumbList per page + MobileApplication |
- Standard (Default): Visible identities.
- Blind (Toggle): Redacts PII prior to scoring, persists via
SecureStore/localStoragerecruitai_blind_mode, syncsPATCH /api/users/me {blind_mode_default}. Mobile toggles live681, Web stages on save — both now replicate. - Rubric: 5-pillar (Tech 35%, Experience 25%, Architecture 15%, Communication 15%, Credentials 10%) — editable calibrator
±5%5..60validationtotal=100%→POST /api/chat Calibrate(RequisitionSpec:111,JobsPage:213).
- Queries: "Who has strongest Kubernetes?", "Compare top 3", "Timeline gaps?" — pronoun resolution, 8-turn history.
- Presets:
screening 4, comparison 3, interviews 4, outreach 3, risks 3, market 3(config/copilotPresets.ts:27,mobile/constants/copilotPresets.ts:31). Total 20. BadgeTOTAL_PRESETS_COUNT. - Async: Mobile dual-mode 90s timeout +
pollJobUntilDone202; Web sync 60s with 2× retry.
- Actions: Shortlist ✓, Offer ★, Reject ✕ →
POST /api/candidates/evaluate{candidate_id, session_id, status, tech_score, comm_score, notes}(unified — was splitstatusvsevaluate). Optimistic +enqueueOfflineMutationdedup,flushOfflineMutationsonhealth/focus. - Filters:
All, Shortlisted, Offered, Rejected+Min Fit [0,50,80]+ sortmatch|name(unified from[0,60,75,85]),searchacross name/headline/skills/email,Grid/Tableviews.
- ReportLab
NumberedCanvas,Page X of Y, role/department/experience/tone/blind badge, stack-ranking matrix, interview probes + salary percentiles, EEOC/GDPR Art.22 disclaimers.POST /reports/generate+GET /reports/session/{id}.
- Now 8 backend APIs (
lib/analyticsApi.ts:20):summary, pipeline, candidates-over-time?days=7|30|90, match-distribution, top-skills?top_n, hiring-velocity, jobs-summary, recent-activity?limit(routes_analytics.py:28). Web previously client-aggregated 20×GET /sessions/{id}→ now parity withmobile/hooks/useAnalytics.ts:46. KPI 8 cards + velocity days, not rates.
- Outreach: Web
OutreachPage:154nowPOST /api/email/send{recipient_email, email_draft: "Subject:…\\n\\nBody"}(wasPOST /api/chatfake) — real SMTP or simulation, plus chat audit. MobileEmailDrafter:126identical + test torecruiter-preview@. - ATS Export:
POST /api/export/atsJSON/CSVconvertAtsToCsvquoting — WebAtsExportModal.tsx:1(preview 4k + blob download) + Mobileats-export.tsx:41(share sheet) onDashboard Jobs+Candidates+Workspace ATS Export.
graph TD;
__start__([User Query]) --> supervisor_agent;
supervisor_agent -.->|load_context, rewrite_jd, fetch_jd_api| jd_agent;
supervisor_agent -.->|screen, count, compare, redflags, query_candidate| screening_agent;
supervisor_agent -.->|interview_questions, salary, email, trend, schedule| interview_salary_agent;
supervisor_agent -.->|finalize_shortlist, book_slot, send_email| hitl_confirm;
supervisor_agent -.->|unrecognized or off-topic| fallback;
jd_agent --> __end__([Response / State Update]);
screening_agent --> __end__;
interview_salary_agent --> __end__;
hitl_confirm --> __end__;
fallback --> __end__;
classDef default fill:#f8f6f2,stroke:#1b2a4a,stroke-width:1.5px;
classDef supervisor fill:#1b2a4a,stroke:#111,color:#fff;
classDef agent fill:#ffffff,stroke:#1b2a4a,stroke-width:1.5px;
classDef hitl fill:#fff7ed,stroke:#ea580c,stroke-width:2px;
classDef fallback fill:#fef2f2,stroke:#dc2626,stroke-width:1.5px;
class supervisor_agent supervisor;
class jd_agent,screening_agent,interview_salary_agent agent;
class hitl_confirm hitl;
class fallback fallback;
| Agent Node | Responsibilities | Intents |
|---|---|---|
| Supervisor | Intent classification, pronoun resolution, confidence <0.60 → fallback, routing | turn routing |
| JD Agent | Ingest PDF/DOCX/TXT, restructure Pydantic, fetch live listings | load_context, rewrite_jd, fetch_jd_api |
| Screening & RAG Agent | pgvector cosine, rubric scoring, gaps/red_flags, matrix |
screen, count, compare, redflags, query_candidate |
| Interview & Salary Agent | Interview kit, salary Tavily search, email draft, schedule | interview_questions, salary, email, trend, schedule |
| HITL Confirm Agent | Blocks send_email, book_slot until yes/confirm |
finalize_shortlist, schedule_interview |
| Fallback Agent | Out-of-scope recovery | other |
Web (Next.js App Router): 20 marketing routes + 6 core = 26 indexed URLs (sitemap.ts:12), priorities 1.0→0.5, excludes /dashboard, /api, /_next/static. robots.ts:11 allows Allow: /features/*, /solutions/*, /guides/*, /compare/* + 8 LLM bots (GPTBot, ClaudeBot, PerplexityBot…), Sitemap: …/sitemap.xml.
Performance SEO: next.config.ts:1 images: avif/webp, cacheTTL 1yr, headers: Cache-Control immutable for static, HSTS preload, compress, optimizePackageImports: lucide-react/framer-motion/recharts, fonts display:swap, preconnect fonts.googleapis, dns-prefetch supabase.
Metadata & Structured Data: layout.tsx:31 title template %s | RecruitAI, 22 keywords (AI recruiting software, AI resume screening, AI ATS, blind hiring), openGraph 1200×630, twitter:summary_large_image, manifest: categories business/productivity, screenshots, shortcuts, related_applications: play. JSON-LD @graph 4 types: SoftwareApplication (BusinessApplication, rating 4.9/127, featureList 8, offers $0 InStock), Organization (logo 512, sameAs GitHub+Play), WebSite (SearchAction → /guides?query={search_term}), FAQPage (5 Q&A) + per-page BreadcrumbList, Article, FAQPage, MobileApplication (/download), Product/Offer×3 (/pricing), ItemList sitelinks (page.tsx:121).
Keyword → Page Map (cannibalization-proof): / → AI recruiting software; /features/ai-resume-screening → AI resume screening; /features/blind-hiring → blind hiring; /features/ats-integration → AI ATS + Greenhouse/Lever/Workday; /solutions/tech-hiring → tech hiring; Guides target guide intent, Compare owns vs/alternative, FAQ own FAQ rich.
Sitelinks: Primary nav Features, Pricing, Download, Guides, FAQ, Support (page.tsx:141) + homepage 4-card grid (page.tsx:425) + footer 4-col SEO links + ItemList SiteNavigationElement hint + high sitemap priorities → Google 2×2 sitelinks under main result.
Mobile ASO (Google Play): Title RecruitAI: AI Recruiter & Hiring (30ch), Short AI resume screening, blind hiring & ATS export. Score candidates in seconds. (79ch), Full ~2700ch natural (all 10 roots once). Category Business, PlayStoreUrl: com.recruitai.app, adaptiveIcon + screenshots 1080×1920 (Copilot, Blind toggle, ATS JSON, Analytics funnel) — mobile/app.json:26 intentFilters autoVerify → https://recruitaiofficial.vercel.app/.well-known/assetlinks.json (SHA256), al:android meta in layout.
mobile/
├── app/
│ ├── (app)/(tabs)/
│ │ ├── copilot.tsx # copilot.tsx index.tsx — streaming, @mention regex, typing dots, live agent strip
│ │ ├── candidates.tsx # Decision cards, StatusFilter, ActiveJdCard, FAB upload
│ │ ├── workspace.tsx # 5-Segment: Requisition, Compare, Schedule, Email, Intel (LiveIntelligenceCard)
│ │ └── analytics.tsx # Gifted Charts: KPI 8 + Funnel + Ingestion 7/30/90 + Histogram + TopSkills + JobsTable + Activity
│ └── (app)/modals/
│ ├── profile-settings.tsx # 4-Tab: Profile/Preferences/Security&GDPR/Diagnostics (ping, backend URL, latency)
│ ├── candidate-inspector # Resume, scores, red_flags, delete
│ ├── ats-export.tsx # JSON|CSV share via expo-sharing
│ └── report-preview.tsx # PDF Dossier viewer + share
Key Capabilities:
- Profile & Settings
ProfileSettingsModal.tsx:50— Profile (name/email/company/phone/role), Preferences (blind_mode_default,match_threshold [50..95] 8 values,auto_rubric,default_export_format pdf|csv,email_alerts/sound/digest/theme), Security & GDPR (UUID copy,GDPR export GET /privacy/user/exportviafileExport:152,DELETE /privacy/user/accountArt.17), Diagnostics (apiConnected dot,latency ping329,getBackendUrl:1119, buildv1.0.0). - Requisition & Rubric
RequisitionSpec.tsx:52— 5-pillar±5steppers98,Total: {totalWeight}%badge,POST /api/chat Calibrate131→ re-score; UploadDocumentPicker+Paste Text JDmodal532now directPOST /api/ingest/upload-jdvia temp fileuseFileIngestion:205(parity with webuploadJd: file|text). - SlotScheduler
SlotScheduler.tsx:53— 5 modes (Technical/System/Coding/HR/Culture), 5 auto slots,WebBrowser.openBrowserAsyncjoin +Clipboardcopy +cancelInterviewconfirm guards. - Offline Resilience —
offlineStorage:105SecureStorequeueenqueue/get/clear+RecruitContext:75flushOfflineMutationsreplay oncheckApiHealth/AppState active4s/12s/15s polls.
Verified 1:1 after 13 sequential production commits (no batch). Gates per fix: npm run build (46 routes), tsc --noEmit (both), jest 9/38 0, ruff/mypy (backend).
| Domain | Web File | Mobile File | Parity | Fix Commit |
|---|---|---|---|---|
| Outreach SMTP | dashboard/outreach/page.tsx:154 POST /api/email/send |
components/workspace/EmailDrafter.tsx:126 same |
✅ 4bddfa5 |
Was POST /api/chat fake |
| ATS Export | lib/atsExport.ts:1 components/export/AtsExportModal.tsx:1 → POST /api/export/ats |
src/lib/fileExport.ts:28 app/modals/ats-export.tsx:41 |
✅ 3ade8d3 |
Web had 0 consumer |
| Candidate Status | context/RecruitmentContext.tsx:515 POST /candidates/evaluate {tech,comm,status} |
context/RecruitContext.tsx:420 same |
✅ cedd3ce |
Was POST /candidates/{id}/status vs evaluate split |
| JD Paste | context/RecruitmentContext:416 `uploadJd(file |
text)viaFormData` |
hooks/useFileIngestion:205 ingestJobDescriptionFromText via temp file → same endpoint |
✅ 3481ab0 |
| Analytics | app/analytics/page.tsx:19 now 8 fetchAnalytics* + fallback client agg |
hooks/useAnalytics:46 same 8 GET /api/analytics/* |
✅ a0badd7 |
Was 0/8 |
| Offline Queue | lib/sessionStorage:14 recruitai_pending_mutations + RecruitmentContext:544 enqueue/flush on focus/apiConnected |
lib/offlineStorage:105 + RecruitContext:75 |
✅ e007181 |
Web had TTL only |
| Rubric Calibrator | app/dashboard/jobs/page.tsx:213 editable ±5 Total: {totalWeight}% + POST /api/chat Calibrate |
components/workspace/RequisitionSpec:52 5-pillar same |
✅ 737c858 |
Web was read-only |
| Interviews | app/dashboard/interviews/page.tsx:56 POST /api/chat persist + copy/join/cancel |
components/workspace/SlotScheduler:53 5 modes + join/copy/cancel |
✅ 8cc9ff3 |
Was localStorage only |
| Copilot Presets | config/copilotPresets:27 6 categories 20 prompts (+ market) |
constants/copilotPresets:31 6/20 |
✅ 5d9fd60 |
Was 17 vs 20 |
| Profile | AuthContext:21 department + ProfileTab:43 avatar + SecurityTab:15 GDPR export/delete + Diagnostics ping |
AuthContext:26 same |
✅ 7847a45 |
avatarUrl/theme/digest off harmonized |
Blocking 0 remain — core hiring loop (ingest file+text → rubric calibrate → copilot @mention + 20 presets → triage with queue → compare → schedule → outreach SMTP → ATS+PDF → analytics → GDPR) is 100% loss-free cross-device (4s session, 12s sessions, 15s health). Minor cosmetic red_flags chips remain polish.
- GDPR Art.15/20:
GET /api/privacy/user/export(recruiter archive) +GET /api/privacy/candidates/{id}/export(candidate) — WebSecurityTab: Downloadblob + MobileProfileSettingsModal:254expo-sharing. - Art.17 Erasure:
DELETE /api/privacy/user/account+DELETE /api/privacy/candidates/{id}— MobileProfileSettingsModal:273Alertconfirm →logout(), WebSecurityTabconfirm 2× →onLogout. - Art.22 HITL:
routes_evaluate.py:405actor==automated_agent && rejected → 403; UI requires explicit confirm (OutreachshowConfirmModal, triageshowModalGDPR). - NYC LL144 Bias Audit:
GET /api/privacy/bias-auditcomputesp20/p80,mean/median, buckets,impact_ratio4/5ths; retentionPOST /api/privacy/retention-policyTTL 180/365 days prunes.
| Layer | Technologies | Purpose |
|---|---|---|
| Web Frontend | Next.js 16 (App Router), React 19, TypeScript | metadataBase, sitemap.ts, robots.ts, manifest, next/image avif |
| Mobile Application | Expo SDK 57, React Native 0.86, TypeScript | SecureStore offline queue, haptics, XMLHttpRequest multipart boundary |
| Styling & Design | Tailwind CSS v4, NativeWind v4, Lucide Icons | #F8F6F2/#111111/#1B2A4A luxury system |
| Data Visualization | Recharts (Web), Gifted Charts (Mobile) | Funnel, TimeSeriesPoint, MatchBucket, SkillDemand, HiringVelocity |
| Backend API Server | FastAPI, Uvicorn, Python 3.11+ | 8 routers /api, Form session_id, 207 Multi-Status |
| AI & Multi-Agent | LangGraph, LangChain, Pydantic v2 | StateGraph builder.py, router_node.py, HITL hitl_confirm |
| LLM Inference & Failover | Google Gemini 2.5 Flash / 3.1 Flash Lite | llm_router.py exponential backoff, verify_provider_compliance |
| Embeddings & Vector Store | Google Gemini Embedding 2 Cloud, pgvector | 384-d MRL, vector(384) IVFFlat |
| Database & Auth | Supabase (PostgreSQL), Supabase Auth | RLS auth.uid()=user_id, match_resume_chunks(vector(384),threshold,count) |
| Reporting & Search Tools | ReportLab, Tavily Search API | NumberedCanvas, Tavily salary/postings/market |
| SEO Infrastructure | Next metadata + JSON-LD + sitemap/robots + manifest | lib/seo.ts canonical, buildMetadata, breadcrumbJsonLd, Article |
-- 1. Core Relational Entities
public.jobs -- Normalized job postings (title, raw_jd, jd_structured, status)
public.candidates -- Canonical applicant profile (full_name, email, raw_resume_text, metadata)
public.resume_chunks -- 384-dim vector embeddings tied to candidates (IVFFlat indexed)
public.applications -- Join table candidate <-> job with match_score & pipeline status + match_reasoning.rubric
public.interviews -- Scheduled interview slots (application_id, scheduled_at, duration, mode, link)
public.chat_sessions -- Recruiter campaign sessions scoped to user_id and active job_id + scheduled_interviews JSON
public.chat_messages -- Full conversational message history with turn metadata
public.users -- Recruiter tenant profile (company, department, preferences, avatar_url)
-- 2. pgvector Match Function
CREATE OR REPLACE FUNCTION public.match_resume_chunks(
query_embedding vector(384),
match_threshold float,
match_count int,
filter_candidate_id uuid default null,
filter_user_id uuid default null
) RETURNS TABLE (
id uuid,
candidate_id uuid,
full_name text,
chunk_text text,
similarity float
) LANGUAGE sql STABLE ...
-- 3. Row-Level Security (RLS)
ALTER TABLE public.jobs ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users manage their own jobs" ON public.jobs
FOR ALL USING (auth.uid() = user_id);RecruitAI/
├── backend/
│ ├── app/
│ │ ├── api/ # 8 Routers
│ │ │ ├── routes_analytics.py # 8 KPIs: summary, pipeline, time-series, match-dist, top-skills, velocity, jobs, activity
│ │ │ ├── routes_chat.py # LangGraph sync + async 202 job + SSE stream + job_id
│ │ │ ├── routes_evaluate.py # POST /candidates/evaluate (rubric), ATS export, status (GDPR Art.22)
│ │ │ ├── routes_ingest.py # PDF/DOCX/TXT upload-jd + upload (207 Multi-Status)
│ │ │ ├── routes_email.py # POST /email/send (SMTP or simulation)
│ │ │ ├── routes_privacy.py # GDPR Art.15/17/20, NYC LL144 bias-audit, retention run
│ │ │ ├── routes_reports.py # ReportLab Dossier
│ │ │ ├── routes_sessions.py # Sessions CRUD + PATCH rename + reset-all
│ │ │ └── routes_users.py # GET/PATCH /users/me (department, preferences)
│ │ ├── core/ # auth.py (JWT HS256), config.py, llm_router.py, logging.py
│ │ ├── graph/ # builder.py, router_node.py, nodes/* (5 agents)
│ │ ├── rag/ # advanced_rag.py, embeddings.py (384-d), vector_store.py, fallback_db.py
│ │ ├── schemas/ # Pydantic: Candidate, JobDescription, User (department)
│ │ └── services/ # parsers, retention, report builder
│ ├── scripts/init_db.sql # Schema, RLS, RPC, analytics RPCs
│ ├── tests/ # 159 tests collected
│ └── requirements.txt
│
├── frontend/ # Next.js 16 App Router (46 static routes)
│ ├── src/
│ │ ├── app/ # Landing + 20 SEO (features/solutions/guides/compare/pricing/faq/download) + Dashboard (copilot/candidates/compare/jobs/interviews/outreach) + Analytics + SEO (sitemap/robots/manifest)
│ │ │ ├── features/ # hub + ai-resume-screening/ai-candidate-screening/blind-hiring/ats-integration/recruitment-automation
│ │ │ ├── solutions/ # tech-hiring/startups/enterprise/hr-teams
│ │ │ ├── guides/ # hub + ai-recruiting/resume-screening/candidate-screening/ats-guide
│ │ │ ├── compare/ # greenhouse-vs-recruitai/lever-vs-recruitai
│ │ │ └── analytics/ # now 8 API fetchAnalytics* + fallback client agg
│ │ ├── components/
│ │ │ ├── seo/ # JsonLd, Breadcrumbs, InternalLinks, SkipLink, AnalyticsScripts, MarketingNav
│ │ │ ├── export/ # AtsExportModal (new parity)
│ │ │ ├── profile/ # ProfileTab (avatar), PreferencesTab ([50..95] 8 thresholds), SecurityTab (GDPR + Diagnostics), NotificationsTab
│ │ │ ├── copilot/ # CopilotCanvas, CopilotDock, PresetsDeckDrawer (20 presets), CandidateMentionPopover
│ │ │ ├── analytics/ # KpiCard, PipelineFunnel, CandidatesOverTime, MatchDistribution, TopSkills, HiringVelocity, JobsTable, ActivityFeed
│ │ │ └── layout/ # AppSidebar, AppHeader
│ │ ├── context/ # AuthContext (department, avatar), RecruitmentContext (POST evaluate, offline queue, renameSession)
│ │ ├── lib/ # seo.ts (canonical, buildMetadata, breadcrumb/faq/Article), analytics.ts, analyticsApi.ts (8), atsExport.ts (new), apiClient.ts (retry/backoff/401 refresh), sessionStorage.ts (offline queue)
│ │ └── config/ # site.ts (PUBLIC_ROUTES), copilotPresets.ts (6 categories 20 prompts)
│ └── package.json # Next.js 16, React 19, Tailwind CSS v4, Recharts, Framer Motion, gsap
│
├── mobile/ # Expo 57 / React Native (100% parity + offline-first)
│ ├── app/
│ │ ├── (app)/(tabs)/ # copilot (index) / candidates / workspace (5-Segment) / analytics (8 APIs + lookback)
│ │ └── (app)/modals/ # profile-settings (4-Tab), candidate-inspector, ats-export, report-preview
│ ├── src/
│ │ ├── components/ # workspace/RequisitionSpec (editable rubric + paste JD direct), SlotScheduler, CompareMatrix (red_flags), CandidateCard (consent), chat/*, modals/*
│ │ ├── context/ # AuthContext (SecureStore cache, refreshSession) & RecruitContext (queue, 4s/12s/15s polls)
│ │ ├── lib/ # apiClient (45s, 2× backoff, 401 refresh, XHR multipart), offlineStorage, fileExport (ATS + GDPR), haptics
│ │ └── constants/ # copilotPresets (6/20), theme
│ ├── __tests__/ # 9 suites/38 tests
│ └── package.json # Expo 57, React Native 0.86, NativeWind v4
│
├── docs/ (ignored in git)
└── plans/ (ignored)
- Python 3.10+
- Node.js 18+ & npm (or pnpm)
- Supabase Account (PostgreSQL with
vectorandpgcryptoextensions) - API Keys for Google Gemini and Tavily, SMTP env for
POST /email/send
- Open your Supabase Dashboard -> SQL Editor.
- Execute
backend/scripts/init_db.sql— initializes 8 tables, pgvector indexes, RLS policies,match_resume_chunksRPC, analytics RPCs.
cd backend
python3 -m venv venv; source venv/bin/activate; pip install -r requirements.txtConfigure backend/.env (from .env.example):
GEMINI_API_KEY=your_gemini_api_key
TAVILY_API_KEY=your_tavily_api_key
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
SUPABASE_JWT_SECRET=your_supabase_jwt_secret
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your_smtp_user
SMTP_PASSWORD=your_smtp_pass
SMTP_SENDER=noreply@recruitai.com
USE_LOCAL_AUTH=falseLaunch:
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reloadDocs at http://localhost:8000/docs.
cd ../frontend; npm installConfigure frontend/.env.local:
NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
NEXT_PUBLIC_SITE_URL=https://recruitaiofficial.vercel.app
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXXRun:
npm run devDashboard at http://localhost:3000 — 46 static routes prerendered.
cd ../mobile; npm installConfigure mobile/.env:
EXPO_PUBLIC_BACKEND_URL=http://<YOUR_LOCAL_IP>:8000
EXPO_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_keyStart:
npx expo start- Press
afor Android emulator orifor iOS simulator, or scan QR with Expo Go.
USE_LOCAL_AUTH=true— Bypasses Supabase Auth, binds toLOCAL_DEV_USER_ID,mock-token.- Embedded SQLite Mirror (
FallbackSupabaseClient): If no Supabase creds, mirrors PostgreSQL intobackend/data/local_dev.db. - Mobile Offline Queue:
expo-secure-storededup bycandidateId→ replayPOST /candidates/evaluateonapiConnected/AppState active. - Web Offline Queue:
localStoragerecruitai_pending_mutations→ replay onhealth/focus(new parity).
All protected endpoints require Authorization: Bearer <token> — frontend/lib/apiClient now retries 2× with 400*2^attempt + 401 refreshSession + 502-504 backoff (parity with mobile/lib/apiClient XHR multipart).
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/chat |
Sync or async 202 job + GET /chat/jobs/{id} poll + SSE stream; no longer faked outreach. |
GET |
/api/sessions |
Lists recruiter campaigns. |
POST |
/api/sessions |
Creates new blank campaign. |
GET |
/api/sessions/{id} |
Hydrated session (JD, resumes, shortlist, interviews, history). |
PATCH |
/api/sessions/{id} |
Rename title — now wired in Web renameSession too. |
DELETE |
/api/sessions/{id} |
Deletes campaign with cascade. |
POST |
/api/sessions/reset-all |
Resets all workspace data. |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/ingest/upload-jd |
Multipart file + session_id (Form); Web `file |
POST |
/api/ingest/upload |
Candidate resumes (15MB max, magic bytes, 207 Multi-Status per-file). |
POST |
/api/reports/generate |
ReportLab payload → NumberedCanvas PDF. |
GET |
/api/reports/session/{id} |
Streams dossier for session (blind mode toggle). |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/privacy/user/export |
Art.15/20 Recruiter archive — Web SecurityTab: Download blob + Mobile ProfileSettingsModal:254 share. |
DELETE |
/api/privacy/user/account |
Art.17 Purge — both platforms confirm 2×. |
DELETE |
/api/privacy/candidates/{id} |
Art.17 Candidate erase — both contexts. |
GET |
/api/privacy/candidates/{id}/export |
Art.15/20 Candidate portability. |
GET |
/api/privacy/bias-audit |
NYC LL144 impact ratios (wired for future SecurityTab → Run Bias Audit). |
POST |
/api/privacy/retention-policy |
TTL pruning 180/365 days. |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/analytics/summary |
14 fields: total_candidates/active_jobs/screening_rate/offer_rate |
GET |
/api/analytics/pipeline |
4 stages with percentage |
GET |
/api/analytics/candidates-over-time?days=7|30|90 |
TimeSeriesPoint {day,count} |
GET |
/api/analytics/match-distribution |
MatchBucket Top Tier 90-100% etc. |
GET |
/api/analytics/top-skills?top_n |
SkillDemand {skill,demand_count} weighted |
GET |
/api/analytics/hiring-velocity |
avg_days_to_shortlist/interview/offer |
GET |
/api/analytics/jobs-summary |
JobSummaryRow per-job aggregates |
GET |
/api/analytics/recent-activity?limit |
ActivityEvent 3 sources merged |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/email/send |
{recipient_email, email_draft:"Subject:…\\n\\nBody"} — real SMTP or simulation, status ✅/ℹ️ — both platforms. |
POST |
/api/export/ats |
{format: json|csv, session_id} → evaluations map tech_score/comm_score/status — Web AtsExportModal + Mobile ats-export.tsx. |
POST |
/api/candidates/evaluate |
Unified triage: {candidate_id, session_id, status, tech_score, comm_score, notes} — Web handleSetStatus now tech 4/5, comm 3/5 like Mobile + offline dedup queue. |
# 1. Backend Pytest Suite (159 tests)
cd backend && pytest -v
# 2. Backend Linter & Type Checker
ruff check app/; mypy app/ --ignore-missing-imports
# 3. Mobile Jest Suite (38 tests across 9 suites)
cd ../mobile && npm test
# 4. Mobile TypeScript Strict
npx tsc --noEmit
# 5. Web Frontend Build + TypeScript Strict
cd ../frontend && npm run build && npx tsc --noEmit
# → 46 static routes prerendered, 0 TS errors, per-fix gates: build 6.1s, LCP <2.5s via avif/webp- Interactive Presentation Deck: View Canva Slides
- Full Video Demonstration: Watch Video Walkthrough
- GitHub Repository: Sri-dinesh/RecruitAI
- Live Platform: recruitaiofficial.vercel.app
Built with precision by Sri-dinesh for modern talent teams.
RecruitAI — The Intelligence Layer for Modern Recruiting. — Web & Mobile 100% parity, SEO/ASO production, 100k organic ready, public launch in 3 days.



