Skip to content

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 --> Q

Candidate 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 statuses

Usage-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]