Key Flow Diagrams
Job Creation Flow
mermaid
sequenceDiagram
actor R as Recruiter
participant FE as PeerZoom (Frontend)
participant API as API-honeyhimself
participant DB as MongoDB
R->>FE: Open "Create Job" page
FE->>API: GET /v1/stage-types
API->>DB: StageTypeConfig.find({ isActive: true }).sort({ order: 1 })
DB-->>API: [StageTypeConfig docs]
API-->>FE: [{key, displayName, schedulingConfig, toolsConfig, questionBankConfig, ...}]
Note over FE: Frontend renders stage type dropdown dynamically.<br/>All 6 types shown — no plan filtering.<br/>No hardcoded STAGE_TYPES array needed.
R->>FE: Fill title, description, add stages, configure each stage
Note over FE: For each stage, frontend shows the correct<br/>config panel based on stageTypeKey from API data.
R->>FE: Click "Create Job"
FE->>API: POST /v1/jobs { title, description, stages: [{name, stageTypeKey, screeningConfig|dsaConfig|...}] }
API->>DB: Check active job count vs org.featureSnapshot.maxActiveJobs → 403 if at limit
API->>DB: For each stage question ref → findOrCreateQuestion()
API->>DB: JobOpening.create({...stages with resolved questionIds})
DB-->>API: JobOpening doc
API-->>FE: { success: true, data: job }Candidate Scheduling & Stage Progression
mermaid
sequenceDiagram
actor R as Recruiter
participant API as API-honeyhimself
participant DB as MongoDB
participant Q as Email Queue
%% Initial scheduling
R->>API: POST /v1/interviews { jobId, stageId, participantEmail, ... }
API->>DB: Find or create Participant by email
API->>DB: Find or create CandidatePipeline (jobOpeningId + participantId, unique)
Note over API,DB: CandidatePipeline.stageProgression is initialized<br/>with 'pending' for all stages on first creation.
Note over API,DB: Stage status guard — reads stageProgression[stageIndex].status:<br/>'pending' → 409 locked (unlock first)<br/>'invited'/'in_progress'/'completed' → 409 cannot re-invite<br/>'unlocked' → proceed
API->>DB: Create Interview { stageId, stageTypeKey(snapshot), candidatePipelineId, screeningToken, declineToken, ... }
API->>DB: CandidatePipeline.stageProgression[stageIndex].status = 'invited'
API->>DB: CandidatePipeline.stageProgression[stageIndex].interviewId = interview._id
API->>Q: Enqueue email with attend link (${FRONTEND_URL}/candidate/screening?token=...) AND decline link (${FRONTEND_URL}/candidate/decline/...)
API-->>R: Interview created
%% Stage completion (async screening example)
participant C as Candidate
C->>API: POST /v1/screening/:token/submit { responses[] }
API->>DB: Interview.stageData.screeningResponses = responses
API->>DB: Interview.status = 'completed'
API->>DB: CandidatePipeline.stageProgression[stageIndex].status = 'completed'
Note over API: Trigger inline AI evaluation (not enqueued — runs synchronously after submit)
%% Recruiter unlocks next stage (with optional force flag)
R->>API: POST /v1/pipeline/:pipelineId/unlock-stage { stageIndex: 1, force?: true }
Note over API,DB: Without force: 409 if any preceding stage not 'completed'<br/>With force: cascade-complete all preceding incomplete stages
API->>DB: CandidatePipeline.stageProgression[1].status = 'unlocked'
API->>DB: CandidatePipeline.currentStageIndex = 1
API-->>R: Pipeline updated
Note over R: Recruiter can now schedule Stage 2 for this candidate.Automated Screening Flow
mermaid
flowchart TD
A[Recruiter creates job with\nautomated_screening stage] --> B[Recruiter schedules screening\nfor candidate]
B --> C{sendAutomatedLink?}
C -- Yes --> D[Email: link to screening session\nwith screeningToken]
C -- No --> E[Email: inform only\nno link sent]
D --> F[Candidate clicks link → validates token]
F --> G[Load effective stage config:\n1. Check Interview.stageOverrides.screeningConfig\n2. Fall back to JobOpening.stages.screeningConfig if no overrides]
G --> H{Question time limit\nset per question?}
H -- Yes --> I[Show timed Q&A interface]
H -- No --> J[Show untimed Q&A interface]
I --> K[Candidate submits responses]
J --> K
K --> L[POST /v1/screening/:token/submit]
L --> M[Save responses to\nInterview.stageData.screeningResponses]
M --> N[Trigger inline AI evaluation\nPhase 1A: runs synchronously\nPhase 6: moved to BullMQ queue]
N --> O[AI scores each response\nGenerates Interview.stageData.screeningAiReport\nfields: score, summary, strengths, concerns, recommendation]
O --> P[Interview.status = completed]
P --> Q[CandidatePipeline.stageProgression\nstatus = completed]
Q --> R[Push notification to Recruiter]
E --> S[Recruiter calls candidate manually]
S --> T[Recruiter marks stage complete manually]
T --> QCandidate Dashboard Access Flow
mermaid
sequenceDiagram
actor C as Candidate
participant Email as Email Client
participant FE as PeerZoom (Frontend)
participant API as API-honeyhimself
participant FB as Firebase Auth
participant DB as MongoDB
%% Phase 1: Recruiter schedules — Participant pre-created
Note over API,DB: Recruiter schedules interview for alice@example.com
API->>DB: Participant.findOneAndUpsert({ email: 'alice@example.com' })<br/>→ Participant created with authId = null (unclaimed)
API->>DB: CandidatePipeline created with participantId
API->>Email: Send invite email (with screeningToken link for that one session)
%% Phase 2: Candidate attends session (no auth needed)
C->>Email: Clicks screening link
Email->>FE: /candidate/screening?token=abc123tok
FE->>API: GET /v1/screening/abc123tok (no auth header)
API->>DB: Interview.findOne({ screeningToken: 'abc123tok' })
DB-->>API: Interview + stage config
API-->>FE: Session data
FE-->>C: Screening interface — candidate submits answers
%% Phase 3: Candidate registers / logs in to see dashboard
C->>FE: Navigates to /candidate/login
FE->>FB: Firebase sign-in (email + password / Google OAuth)
FB-->>FE: Firebase JWT (idToken)
FE->>API: GET /v1/candidate/dashboard<br/>Authorization: Bearer <Firebase idToken>
API->>FB: Verify JWT → { uid, email }
API->>DB: Participant.findOne({ email: 'alice@example.com' })
alt Participant exists (pre-created by recruiter) and authId is null
API->>DB: Participant.updateOne({ authId: uid, userId: newUserId })
Note over API: All past pipelines now linked to her account
else Participant already claimed (re-login)
Note over API: Verify uid matches — attach req.participant
else No participant record yet (registered before any recruiter invite)
API->>DB: Participant.create({ email, authId: uid })
end
API->>DB: CandidatePipeline.find({ participantId }) sorted by lastActivityAt desc
DB-->>API: All pipelines (cross-org)
API->>API: Apply candidateProjection() — strips internal<br/>status, notes, AI scores, feedbacks
API-->>FE: [{jobSnapshot, candidateFacingStatus, stageProgression[].candidateStatus}]
FE-->>C: Dashboard: all applications with stage statusesUsage-Cap Enforcement Flow
All 6 stage types are available on every plan. Monetization is enforced via numeric usage limits stored in Organization.featureSnapshot.
mermaid
flowchart TD
A[Recruiter opens\nCreate Job page] --> B[GET /v1/stage-types]
B --> C[API returns all active StageTypeConfigs\nno plan filtering]
C --> D[Frontend shows all 6 stage types\nin dropdown]
E[Recruiter clicks\nCreate Job] --> F[POST /v1/jobs]
F --> G{Active jobs ≥\nfeatureSnapshot.maxActiveJobs?}
G -- Yes --> H[403: Active job limit reached]
G -- No --> I[JobOpening created]
J[Recruiter schedules\ninterview for candidate] --> K[POST /v1/interviews]
K --> L{Candidates in job ≥\nfeatureSnapshot.maxCandidatesPerJob?}
L -- Yes --> M[403: Candidate limit reached]
L -- No --> N{Interviews this month ≥\nfeatureSnapshot.maxInterviewsPerMonth?}
N -- Yes --> O[403: Monthly interview limit reached]
N -- No --> P[Interview created]
Q[Plan upgrade via Stripe] --> R[Stripe webhook: subscription.updated]
R --> S[Update Organization.featureSnapshot\nfrom new Plan.features]
S --> T[Higher limits take effect immediately]Recruiter: Unified Candidate View
mermaid
graph TD
A[GET /v1/pipeline?jobId=X] --> B[List all CandidatePipeline\ndocs for this job]
B --> C{For each pipeline}
C --> D[Show candidate name + email\nfrom Participant ref]
C --> E[Show pipeline.status\ne.g. active / shortlisted]
C --> F[Show stageProgression[]\nas a status timeline]
G[GET /v1/pipeline/:id] --> H[Full pipeline detail]
H --> I[Populate all Interview refs\nin stageProgression]
I --> J{For each stage Interview}
J --> K{stageTypeKey?}
K -- automated_screening --> L[Show Q&A pairs +\nscreeningAiReport]
K -- live_1on1 --> M[Show feedback ratings\nand comments]
K -- technical_dsa --> N[Show per-problem\ntest pass rates]
K -- technical_ai_assisted --> O[Show Q&A transcript\n+ aiReport]
P[PATCH /v1/pipeline/:id/status] --> Q[Set global status:\nshortlist / reject / hire]
Q --> R[Auto-set candidateFacingStatus\nvia status mapping]