{"openapi":"3.0.3","info":{"title":"Freehire FSP recruitment API","version":"0.6.0","description":"Freehire FSP pilot. Real candidate-authored evidence is separate from synthetic FSP achievements. Verification is versioned and moderator-authored. Invitations are not employment offers."},"servers":[{"url":"/"}],"paths":{"/fsp-api/candidates":{"get":{"summary":"List consented candidates with assessment-confirmed categories and grades","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid input"},"401":{"description":"Freehire session required"},"403":{"description":"Origin or ownership check failed"},"409":{"description":"Duplicate offer or invalid transition"},"429":{"description":"Freehire rate limit exceeded"}},"parameters":[{"name":"q","in":"query","schema":{"type":"string"},"description":"Title or skill substring"},{"name":"role","in":"query","schema":{"type":"string"},"description":"Category derived from achievements"},{"name":"city","in":"query","schema":{"type":"string"},"description":"Exact city"},{"name":"grade","in":"query","schema":{"type":"string"},"description":"Repeat for multiple grades"},{"name":"mode","in":"query","schema":{"type":"string"},"description":"Repeat for multiple work modes"},{"name":"salary","in":"query","schema":{"type":"integer"},"description":"Maximum monthly salary in RUB"},{"name":"verified","in":"query","schema":{"type":"string"},"description":"1 requires achievements"},{"name":"saved","in":"query","schema":{"type":"string"},"description":"1 restricts to authenticated account favorites"},{"name":"sort","in":"query","schema":{"type":"string"},"description":"salary or evidence"},{"name":"limit","in":"query","schema":{"type":"integer"},"description":"Default 20, maximum 100"},{"name":"offset","in":"query","schema":{"type":"integer"},"description":"Zero-based offset; bounded by API window"},{"name":"matchSkill","in":"query","schema":{"type":"array","maxItems":40,"items":{"type":"string","minLength":1,"maxLength":100}},"style":"form","explode":true,"description":"At least one exact case-insensitive trimmed declared skill must match. Rank by distinct matched skills before the usual tie breakers. Existing skill filters still require all selected values."}]}},"/fsp-api/offers":{"get":{"summary":"List offers in the current FSP account","responses":{"200":{"description":"Account-owned offers, meta {total, limit, offset, ignored_params?}, counts {all, pending, accepted, declined} before search/status filters, and pendingIncoming for the signed-in account across all incoming offers. Stable ordering by createdAt and ID descending. Outcomes are lifetime unique candidate/job pairs across the authorized view, before filters and pagination: pairs, interested, interviewed, relevant, offered, started. Unlinked counts invitations with no job and is excluded from pairs. Confirmed dates must agree; relevance requires both explicit positive assessments on the same interview. Stages are independent, not inferred. Refusals contains candidateTotal, employerTotal, candidateReasons/employerReasons arrays of {reason,count} (five most frequent exact trimmed phrases; null means no explanation), candidateOther/employerOther remainder counts, and expiredWithoutResponse. Counts are invitations, not unique pairs, over the entire authorized view before filtering/pagination. Candidate and employer groups can overlap."},"400":{"description":"Invalid input"},"401":{"description":"Freehire session required"},"403":{"description":"Origin or ownership check failed"},"409":{"description":"Duplicate offer or invalid transition"},"429":{"description":"Freehire rate limit exceeded"}},"parameters":[{"name":"view","in":"query","schema":{"type":"string","enum":["candidate","employer"],"default":"employer"},"description":"Incoming offers for the current candidate, or offers sent by the current employer. Never switches identity."},{"name":"q","in":"query","schema":{"type":"string"},"description":"Literal substring in vacancy title, company, message or candidate handle. Private names are not searched."},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","accepted","declined","expired"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"minimum":0},"description":"Offset plus limit must not exceed 10000."}]},"post":{"summary":"Send an offer for an employer-owned vacancy","responses":{"201":{"description":"Successful response"},"400":{"description":"Invalid input"},"401":{"description":"Freehire session required"},"403":{"description":"Origin or ownership check failed"},"409":{"description":"Duplicate offer or invalid transition"},"429":{"description":"Freehire rate limit exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["candidateId","jobId","salary","message","conditions","expiresInDays"],"properties":{"candidateId":{"type":"string"},"salary":{"type":"integer","minimum":10000,"maximum":2000000},"message":{"type":"string"},"jobId":{"type":"string","description":"Public slug of an open employer-owned Freehire vacancy."},"conditions":{"type":"object","required":["salaryMax","taxBasis","mode","geography","employment","tasks","stages","nextStep"],"properties":{"salaryMax":{"type":"integer","minimum":10000,"maximum":2000000},"taxBasis":{"type":"string","enum":["gross","net"]},"mode":{"type":"string","enum":["Удалённо","Гибрид","Офис"]},"geography":{"type":"string","minLength":2,"maxLength":200},"employment":{"type":"string","minLength":2,"maxLength":100},"tasks":{"type":"string","minLength":20,"maxLength":2000},"stages":{"type":"string","minLength":5,"maxLength":500},"nextStep":{"type":"string","minLength":5,"maxLength":500},"mentor":{"type":"boolean"},"requiredSkills":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":40}},"trainableSkills":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":40}}}},"expiresInDays":{"type":"integer","minimum":1,"maximum":30}}}}}},"description":" jobId is optional (empty string for a direct invitation); title (2–120 characters) is required without a vacancy. Salary and conditions.salaryMax are a monthly RUB range. The company identity and employer contact come from the verified company account. Only confirmed, visible candidates are eligible."},"patch":{"summary":"Respond in the same FSP account","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid input"},"401":{"description":"Freehire session required"},"403":{"description":"Origin or ownership check failed"},"409":{"description":"Duplicate offer or invalid transition"},"429":{"description":"Freehire rate limit exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string"},"status":{"type":"string"},"declineReason":{"type":"string","maxLength":500}},"example":{"id":"offer-uuid","status":"accepted"}}}}}}},"/fsp-api/achievements":{"post":{"summary":"Import local mock achievements; idempotent","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid input"},"401":{"description":"Freehire session required"},"403":{"description":"Origin or ownership check failed"},"409":{"description":"Duplicate offer or invalid transition"},"429":{"description":"Freehire rate limit exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["memberId"],"properties":{"memberId":{"type":"string"}},"example":{"memberId":"FSP-DEMO-001"}}}}}}},"/api/v1/auth/register":{"post":{"summary":"Start registration without revealing account existence","responses":{"202":{"description":"Accepted"},"400":{"description":"Invalid request or confirmation"},"429":{"description":"Rate limit exceeded"}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","format":"password","minLength":8,"maxLength":72}},"required":["email","password"]}}}}}},"/api/v1/auth/register/confirm":{"post":{"summary":"Confirm email with native single-use code before starting a session","responses":{"204":{"description":"Accepted"},"400":{"description":"Invalid request or confirmation"},"429":{"description":"Rate limit exceeded"}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","format":"password","minLength":8,"maxLength":72},"code":{"type":"string","pattern":"^[0-9]{6}$"}},"required":["email","password","code"]}}}}}},"/api/v1/fsp/security/mfa":{"get":{"summary":"Read own MFA status","responses":{"200":{"description":"Success"},"401":{"description":"Invalid authentication or second factor"},"403":{"description":"Forbidden"},"428":{"description":"Second factor required"},"429":{"description":"Temporarily locked or rate limited"}},"security":[{"freehireSession":[]}]}},"/api/v1/fsp/security/mfa/setup":{"post":{"summary":"Create encrypted pending TOTP setup; current password required","responses":{"200":{"description":"Success"},"401":{"description":"Invalid authentication or second factor"},"403":{"description":"Forbidden"},"428":{"description":"Second factor required"},"429":{"description":"Temporarily locked or rate limited"}},"security":[{"freehireSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string","format":"password","maxLength":72}},"required":["password"]}}}}}},"/api/v1/fsp/security/mfa/enable":{"post":{"summary":"Confirm TOTP and return one-time recovery codes; revokes old sessions","responses":{"200":{"description":"Success"},"401":{"description":"Invalid authentication or second factor"},"403":{"description":"Forbidden"},"428":{"description":"Second factor required"},"429":{"description":"Temporarily locked or rate limited"}},"security":[{"freehireSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string","format":"password","maxLength":72},"code":{"type":"string","maxLength":32}},"required":["password","code"]}}}}}},"/api/v1/fsp/security/mfa/disable":{"post":{"summary":"Disable MFA with password and second factor; unavailable to privileged accounts","responses":{"204":{"description":"Success"},"401":{"description":"Invalid authentication or second factor"},"403":{"description":"Forbidden"},"428":{"description":"Second factor required"},"429":{"description":"Temporarily locked or rate limited"}},"security":[{"freehireSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string","format":"password","maxLength":72},"code":{"type":"string","maxLength":32}},"required":["password","code"]}}}}}},"/api/v1/auth/login":{"post":{"summary":"Native password login with optional required TOTP or recovery code","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","format":"password"},"mfaCode":{"type":"string","maxLength":32}}}}}},"responses":{"200":{"description":"Authenticated session cookie issued"},"401":{"description":"Invalid credentials or second factor"},"403":{"description":"Email confirmation required"},"428":{"description":"mfa_required: submit password and second factor"},"429":{"description":"Account/IP rate limit or second-factor cooldown"}}}},"/api/v1/fsp/evidence":{"get":{"summary":"Own evidence, or public evidence with handle query","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}},"put":{"summary":"Create or update evidence with optimistic version","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/evidence/{id}":{"delete":{"summary":"Delete own evidence with version in JSON body","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/v1/fsp/reviews":{"get":{"summary":"Paginated moderator review queue; MFA required","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/reviews/{id}":{"post":{"summary":"Record a source, ownership or expert review of the exact version","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"description":"Moderator with MFA only, no self-review, published candidate-authored project at the exact version. Expert reviews accept optional skillClaims (up to 10 unique skills). Each claim has skill (1–40 characters), field (contribution or result), and quote (10–2000 characters, an exact substring of that field). Nonempty claims require active source_found and ownership_confirmed reviews of the same version. Reviews are immutable. A current skill confirmation also requires both prerequisites to remain active; editing the project or revoking any prerequisite removes confirmation. Generic legacy reviews with no claims confirm no named skills. This does not change declared skills, qualification, or ranking.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["version","status","basis","limits"],"properties":{"version":{"type":"integer","minimum":1},"status":{"type":"string","enum":["source_found","ownership_confirmed","expert_reviewed"]},"basis":{"type":"string","minLength":10,"maxLength":2000},"limits":{"type":"string","minLength":10,"maxLength":1000},"skillClaims":{"type":"array","maxItems":10,"items":{"type":"object","required":["skill","field","quote"],"properties":{"skill":{"type":"string","minLength":1,"maxLength":40},"field":{"type":"string","enum":["contribution","result"]},"quote":{"type":"string","minLength":10,"maxLength":2000}}}}}}}}}}},"/api/v1/fsp/search-confirmation":{"post":{"summary":"Confirm readiness to communicate for 30 days","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/team":{"get":{"summary":"Own company memberships and invitations","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}},"post":{"summary":"Invite, accept, remove a company membership","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/mail-settings":{"get":{"summary":"Effective email opt-in state","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}},"put":{"summary":"Enable or disable future FSP notification email","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/offers/{id}/events":{"post":{"summary":"Record this side’s date for interview, employment_offer or started","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind","date"],"properties":{"kind":{"type":"string","enum":["interview","employment_offer","started"]},"date":{"type":"string","format":"date"},"relevant":{"type":"boolean","nullable":true,"description":"Interview assessment for the calling side only. Null or omitted clears its assessment. Non-null is rejected for other event kinds."}}}}}}}},"/api/v1/fsp/offers/{id}/contact":{"put":{"summary":"Candidate explicitly shares or revokes contact for an accepted invitation","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]},"get":{"summary":"Read consented contact with current company and membership checks","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Private no-store response"},"401":{"description":"Cookie session required"},"404":{"description":"Unavailable or out of scope"},"429":{"description":"Sensitive read quota exceeded"},"503":{"description":"Audit storage unavailable"}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/v1/fsp/offers/{id}/close":{"post":{"summary":"Employer or accepted team member closes recruitment with an optional reason","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}},"/fsp-api/profile-export":{"get":{"summary":"Download the caller’s private profile and evidence as JSON","responses":{"200":{"description":"Successful response"},"400":{"description":"Invalid fields"},"401":{"description":"Cookie session required"},"403":{"description":"Permission denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/candidate-vacancies":{"get":{"summary":"Personalized open employer vacancies for the signed-in candidate","description":"Returns data {total,jobs}, at most three vacancies after filtering the complete published pool. Requires exact confirmed specialization and grade, all normalized declared mandatory skills, and no known conflict in salary, experience, work mode or location. Unknown conditions must be clarified. Orders by known matching conditions, trainable skill overlap, recency and ID. Publication requires current moderation and a positive RUB/month salary range. Candidate publication is not required.","responses":{"200":{"description":"Personalized list, private and no-store"},"401":{"description":"Cookie session required"}}}},"/api/v1/fsp/pilot-metrics":{"get":{"summary":"Moderator-only pilot readiness and response metrics","description":"Requires a moderator/admin cookie session and enrolled verified MFA in FSP mode. Returns data {asOf,profiles,companies,responses}. Readiness is a current snapshot, never historical activation. Responses consider invitations from the last 30 days aged at least 24 hours; unknown/invalid response timestamps are separate. No-reply invitations stay in the denominator. Includes environment fixtures, not a production market claim. Private, no-store. Activation adds observed, firstReady, notYetReady for new profiles; baseline and baselineReadyObserved for pre-instrumentation profiles; mature14, readyWithin14 and waiting14 for first-readiness timing. Existing profiles have unknown historical activation and are excluded from new-profile cohorts. This is not 14-day qualified-invitation coverage. coverage14 counts distinct candidates with known first activation aged at least 14 days and at least one candidate-assessed suitable invitation created within the first 14 days. Latest assessments may revise the result; delivery is to the account, not verified email delivery. Returns eligible, covered, withoutConfirmed, awaitingAssessment, noInvitations and waiting. demand7 counts explicitly activated vacancies with 7 complete days of observation: eligible, covered (at least 3 distinct interested candidates), oneOrTwo, withoutInterest, waiting. Both invitation and acceptance must occur within the first 7 days, with a current positive candidate-fit assessment; closed vacancies remain included. cohorts14 provides roles and sources arrays of {key,eligible,covered}, using immutable optional self-reported labels at first readiness. Missing/historical labels use an empty key, never inferred attribution; each breakdown sums to the coverage14 denominator. speed14 partitions the same mature candidate cohort by first candidate-assessed suitable invitation time since activation: withinDay (0–24h inclusive), withinWeek (>24h–7d inclusive), withinFortnight (>7d–14d inclusive), withoutConfirmed. The earliest eligible creation time is used, not assessment or email delivery time. All four counts sum to coverage14.eligible; no-event candidates remain included. quality30 reports total, suitable, unsuitable, unassessed and legacy invitation counts created in the last 30 days through now. Legacy means missing structured conditions; all other groups use the current candidate_fit assessment. The four groups partition total, regardless of invitation status; repeated invitations count separately. Voluntary assessment coverage must accompany the unsuitable share among assessed invitations. mail returns current FSP notification counts: unclaimed (currently eligible without a ledger row), retry (eligible with elapsed lease and remaining attempts), waiting (eligible with active lease), exhausted (at least five attempts and elapsed lease without recorded success), ineligible (previously attempted, no longer eligible, not exhausted), delivered (SMTP success recorded). Delivered and exhausted cover retained history. Eligibility follows the worker: verified email, unread FSP notification younger than seven days, explicit consent preceding notification and activity emails enabled. Counts exclude never-attempted ineligible notifications. No addresses or message bodies are returned; SMTP acknowledgement is not inbox delivery and zero counts do not prove scheduling. funnel counts retained unique linked candidate/job pairs across all history, using a suitable invitation, acceptance of a suitable invitation, mutually dated interview, mutually dated job offer and start. Each stage requires the prior flags; relevant interviews are separate. It does not reconstruct missing stages or establish chronology across repeated hiring cycles. withdrawals30 groups actual publication-off transitions recorded since instrumentation by optional reason, using unknown for no answer; counts are events, not distinct candidates.","responses":{"200":{"description":"Aggregate snapshot"},"401":{"description":"Cookie session required"},"403":{"description":"Moderator role required"},"428":{"description":"MFA required"}}}},"/api/v1/fsp/offers/{id}/fit":{"put":{"summary":"Candidate assessment of invitation conditions at receipt","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fit"],"properties":{"fit":{"type":"boolean","nullable":true}}}}}},"responses":{"200":{"description":"Assessment saved; invitation response remains unchanged"},"400":{"description":"Missing or invalid assessment"},"401":{"description":"Cookie session required"},"409":{"description":"Not the addressed candidate or legacy invitation without structured conditions"}}}},"/api/v1/fsp/vacancy-activation/{slug}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read owner-only vacancy activation snapshot","responses":{"200":{"description":"data is null or {activatedAt,responsible,requirements}"},"404":{"description":"Unavailable or foreign vacancy"}}},"post":{"summary":"Confirm vacancy readiness once","description":"Requires an active owner account, open public employer vacancy, skills and positive monthly RUB salary. Repeated valid calls preserve the initial snapshot and date.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["responsible","requirements","budgetConfirmed","requirementsConfirmed"],"properties":{"responsible":{"type":"string","minLength":2,"maxLength":100},"requirements":{"type":"string","minLength":20,"maxLength":2000},"budgetConfirmed":{"type":"boolean","enum":[true]},"requirementsConfirmed":{"type":"boolean","enum":[true]}}}}}},"responses":{"200":{"description":"Readiness recorded"},"400":{"description":"Missing confirmations or invalid fields"},"409":{"description":"Vacancy is foreign, closed, unpaid, private or otherwise not eligible"}}}},"/api/v1/fsp/disputes":{"get":{"summary":"List the authenticated reporter’s review disputes","description":"Private, no-store, 20 records per page using offset. Open first, then newest. Includes original review basis/limits and resolution; no other reporter records.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Moderator role required"},"409":{"description":"Record unavailable, already resolved/reported, or conflicting reviewer"},"428":{"description":"Moderator MFA required"},"429":{"description":"Rate limit exceeded"}}},"post":{"summary":"Report a current active evidence verification","description":"JSON evidenceId, version, status, reason (10–2000 characters). Cookie session and same origin required. Evidence must be owned by caller or publicly visible. One report per reporter/review version/status; revoked and stale reviews cannot be reported. Does not automatically withdraw a status.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Moderator role required"},"409":{"description":"Record unavailable, already resolved/reported, or conflicting reviewer"},"428":{"description":"Moderator MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/reviews/disputes":{"get":{"summary":"Moderator dispute queue","description":"Moderator/admin cookie session with verified MFA in FSP mode. Private, paginated by offset with 20 records; unresolved first.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Moderator role required"},"409":{"description":"Record unavailable, already resolved/reported, or conflicting reviewer"},"428":{"description":"Moderator MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/reviews/disputes/{id}":{"post":{"summary":"Resolve a dispute and optionally withdraw the exact reviewed status","description":"JSON decision keep|revoke and resolution (10–2000 characters). Moderator/MFA, same origin. Reporter and evidence owner cannot decide. One immutable resolution; revoke preserves original review with revokedAt. keep never restores a previously revoked status. Reporter and owner receive generic internal notifications. Public evidence includes revokedAt but excludes report text and resolution. History follows existing account/evidence deletion cascades.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Moderator role required"},"409":{"description":"Record unavailable, already resolved/reported, or conflicting reviewer"},"428":{"description":"Moderator MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/catalog":{"get":{"summary":"Current public pilot catalog","description":"Returns up to 101 explicitly projected jobs ordered by recency. Closed/private jobs are excluded. Employer-authored jobs require creation or confirmation within 30 days; imported sources retain their existing lifecycle. The catalog accepts a bounded limit/offset, text and structured filters. Optional slug selects an exact public identifier using the same publication checks; unavailable identifiers return an empty data array. The UI displays 20 items and uses a sentinel to detect further results.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Role or origin denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/operations":{"get":{"summary":"Own vacancy freshness","description":"Cookie-only, private/no-store. Returns jobs {slug,companyId,title,closed,fresh,confirmedAt}. Entries is always empty for this route. Creation is the initial publication timestamp; updates alone do not renew freshness.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Role or origin denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/job-confirmation/{slug}":{"post":{"summary":"Confirm continued recruiting for 30 days","description":"Only the owner of an open public employer vacancy in an active company can confirm. Clears reminder state. Stale vacancies cannot issue new invitations, appear in candidate recommendations, or participate in the pilot catalog/shortlist.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Role or origin denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}},"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/v1/fsp/reviews/economics":{"get":{"summary":"Private pilot financial journal","description":"Moderator/admin plus verified MFA. Returns all retained employer jobs and ledger entries with canDelete indicating authorship. No automatic estimates or billing integration.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Role or origin denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}},"post":{"summary":"Record a factual pilot operation","description":"Moderator/MFA and same-origin. JSON id UUID (deduplication key), slug, kind payment|refund|labor|variable|acquisition, amount integer RUB 0–100000000, minutes integer 0–100000, date YYYY-MM-DD from 2000 through current UTC date, note up to 500 characters. Labor needs positive minutes; other kinds need positive amount and zero minutes. Journal data belongs to the recording moderator, associated with the employer vacancy.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Role or origin denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/fsp/reviews/economics/{id}":{"delete":{"summary":"Remove an erroneous own journal entry","description":"Moderator/MFA; only the recording user can remove an entry. This is an operational journal, not immutable accounting. Deleting associated jobs/accounts cascades records.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Role or origin denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/v1/fsp/withdrawal":{"get":{"summary":"Latest own publication withdrawal","description":"Returns null while published, otherwise the latest recorded transition as {id,reason}. No earlier history is inferred.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Role or origin denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}},"put":{"summary":"Optional withdrawal feedback","description":"JSON id string and reason empty|found_job|paused|irrelevant|visibility|other. Own latest withdrawal while hidden only; empty clears the reason. Hiding publication does not depend on this answer. Reasons are excluded from public candidate data.","responses":{"200":{"description":"Operation completed"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Role or origin denied"},"409":{"description":"Unavailable or changed record"},"428":{"description":"MFA required"},"429":{"description":"Rate limit exceeded"}}}},"/fsp-api/matching":{"get":{"summary":"Read saved structured search","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"parameters":[{"name":"target","in":"query","required":true,"schema":{"type":"string","enum":["jobs","candidates"]}}]},"post":{"summary":"Parse, refine or apply structured search without an external LLM","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/fsp-api/matching/event":{"post":{"summary":"Record an idempotent synthetic matching observation","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/fsp-api/matching/outreach":{"post":{"summary":"Generate an editable invitation draft; never sends","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/fsp-api/import/{kind}":{"parameters":[{"name":"kind","in":"path","required":true,"schema":{"type":"string","enum":["resume","vacancy"]}}],"get":{"summary":"Read the caller’s latest import preview","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}}},"post":{"summary":"Create an owner-scoped import preview from text","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/fsp-api/import/resume/confirm":{"post":{"summary":"Confirm reviewed CV data into the caller’s profile without changing visibility","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/resume-file":{"post":{"summary":"Extract PDF, DOCX or TXT without publishing or storing the file","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}}}},"/api/v1/fsp/offers/{id}/interview":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read interview options as an invitation participant","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}}},"post":{"summary":"Employer proposes 2–5 future RFC3339 slots after acceptance","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}},"put":{"summary":"Candidate selects one proposed slot exactly once","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/fsp-api/outreach":{"post":{"summary":"Draft an invitation for a visible candidate and the active employer’s own open vacancy; never sends","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owner-scoped result"},"400":{"description":"Invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin or role restriction"},"409":{"description":"Stale revision, already confirmed or unavailable resource"},"429":{"description":"Request quota exceeded"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/matching-profile":{"get":{"summary":"Read owner matching tags without private identity","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Private no-store response"},"401":{"description":"Cookie session required"},"404":{"description":"Unavailable or out of scope"},"429":{"description":"Sensitive read quota exceeded"},"503":{"description":"Audit storage unavailable"}}}},"/api/v1/fsp/privacy-export":{"get":{"summary":"Audited owner profile export","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Private no-store response"},"401":{"description":"Cookie session required"},"404":{"description":"Unavailable or out of scope"},"429":{"description":"Sensitive read quota exceeded"},"503":{"description":"Audit storage unavailable"}}}},"/fsp-api/offers/{id}/contact":{"get":{"summary":"Reveal permitted contact on explicit request","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Private no-store response"},"401":{"description":"Cookie session required"},"404":{"description":"Unavailable or out of scope"},"429":{"description":"Sensitive read quota exceeded"},"503":{"description":"Audit storage unavailable"}},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/v1/fsp/hiring/{slug}":{"get":{"summary":"Read an authorized vacancy decision context; up to five public candidate handles","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"candidate","in":"query","schema":{"type":"array","maxItems":5,"uniqueItems":true,"items":{"type":"string"}},"style":"form","explode":true}]},"put":{"summary":"Explicitly confirm supplementary hiring criteria using version and sourceRevision","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/hiring/{slug}/demand":{"post":{"summary":"Read-only demand preview; optional relaxSkill must be an existing required skill","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/hiring/{slug}/assessment":{"post":{"summary":"Save the current reviewer assessment after a recorded interview","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/hiring/{slug}/feedback":{"post":{"summary":"Record an explicit decision reason without changing requirements or sending messages","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/talent-pool":{"get":{"summary":"Read up to 100 current consent-scoped saved candidates","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}}}},"/api/v1/fsp/talent-pool/{handle}":{"put":{"summary":"Save a candidate category and private note","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/employer-quality":{"get":{"summary":"Read existing company information plus hiring transparency details","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}}},"put":{"summary":"Save hiring transparency details using optimistic version","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/offers/{id}/prescreen":{"get":{"summary":"Read authorized invitation questions and unverified answers","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]},"put":{"summary":"Employer confirms two to five missing facts; candidate confirms answers","description":"Cookie authentication and existing FSP MFA apply. Writes require a matching Origin and application/json. Responses are private, no-store. External AI is not called.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/fsp/features":{"get":{"summary":"Authenticated server capability flags; AI features default off","responses":{"200":{"description":"Boolean feature flags"},"401":{"description":"Cookie session required"}}}},"/api/v1/fsp/qualification":{"get":{"summary":"Read own profile prerequisite, saved industry survey, confirmed category and latest 20 attempts","responses":{"200":{"description":"Owner-scoped qualification, attempt history and active public questions; no answer keys"},"400":{"description":"Invalid specialization, grade or answers"},"401":{"description":"Cookie session required"},"404":{"description":"Attempt not owned by caller"},"409":{"description":"Profile missing, cooldown active, or attempt expired"},"429":{"description":"Request limit reached"}}},"post":{"summary":"Start or resume a 30-minute assessment","description":"Save a candidate profile and an explicit industry/specialization/grade/experience survey first. The requested assessment must match that survey. Survey changes are locked during active attempts. Same specialization/grade retry after 24 hours; confirmed category changes after 90 days. Backend-owned scoring; failure preserves the previous category. Requires same-origin JSON.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["specialization","grade"],"properties":{"specialization":{"type":"string","enum":["Backend","Frontend"]},"grade":{"type":"string","enum":["Junior","Middle","Senior"]}}}}}},"responses":{"200":{"description":"Owner-scoped qualification, attempt history and active public questions; no answer keys"},"400":{"description":"Invalid specialization, grade or answers"},"401":{"description":"Cookie session required"},"404":{"description":"Attempt not owned by caller"},"409":{"description":"Profile missing, cooldown active, or attempt expired"},"429":{"description":"Request limit reached"}}}},"/api/v1/fsp/qualification/{id}/submit":{"post":{"summary":"Submit answers and atomically certify the chosen grade at score >= 80","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answers"],"properties":{"answers":{"type":"object","additionalProperties":{"type":"string"},"description":"Question id to numeric answer or zero-based choice index encoded as a string."}}}}}},"responses":{"200":{"description":"Owner-scoped qualification, attempt history and active public questions; no answer keys"},"400":{"description":"Invalid specialization, grade or answers"},"401":{"description":"Cookie session required"},"404":{"description":"Attempt not owned by caller"},"409":{"description":"Profile missing, cooldown active, or attempt expired"},"429":{"description":"Request limit reached"}}}},"/api/v1/fsp/matching-candidates":{"post":{"summary":"Match assessment-confirmed candidates visible to the employer","description":"Visibility and category/conditions filters apply first. Required skill signals are match, clarified, unknown or mismatch. Results include matchBucket and skillSignals; up to ten per match/clarify/mismatch group. Unknown is not mismatch. Employer-scoped candidate answers never mutate the profile or qualification. Category comes from the assessment. Order: assessment score, current verified projects (capped at three), presence of fresh verified FSP competition results when enabled, preferred skill matches, stable id. Projects require current independent source, ownership and expert reviews without an open dispute. Mock, stale, hidden and revoked evidence cannot improve rank. Does not call a model. Same-origin JSON, cookie session, verified employer and MFA required.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["skills","trainableSkills","excludedRequiredSkills","excludedSkills","roles","excludedRoles","grades","cities","modes","version","experience","salary","experienceOptional","competitions","salaryDirection"],"properties":{"skills":{"type":"array","maxItems":20,"items":{"type":"string"}},"trainableSkills":{"type":"array","maxItems":20,"items":{"type":"string"}},"excludedRequiredSkills":{"type":"array","maxItems":20,"items":{"type":"string"}},"excludedSkills":{"type":"array","maxItems":20,"items":{"type":"string"}},"roles":{"type":"array","maxItems":20,"items":{"type":"string"}},"excludedRoles":{"type":"array","maxItems":20,"items":{"type":"string"}},"grades":{"type":"array","maxItems":20,"items":{"type":"string"}},"cities":{"type":"array","maxItems":20,"items":{"type":"string"}},"modes":{"type":"array","maxItems":20,"items":{"type":"string"}},"version":{"type":"integer","enum":[1]},"experience":{"type":"number","nullable":true,"minimum":0,"maximum":50},"salary":{"type":"number","nullable":true,"minimum":0,"maximum":2000000},"experienceOptional":{"type":"boolean"},"competitions":{"type":"boolean"},"salaryDirection":{"type":"string","enum":["min","max"]}}}}}},"responses":{"200":{"description":"data.total and up to ten data.candidates"},"400":{"description":"Invalid structured conditions"},"401":{"description":"Session required"},"403":{"description":"Verified employer required"},"428":{"description":"MFA required"},"429":{"description":"Matching rate limit reached"}}}},"/api/v1/fsp/validation-studies":{"get":{"summary":"List the caller’s validation-study memberships","responses":{"200":{"description":"Up to 100 study summaries"}},"security":[{"freehireSession":[]}]},"post":{"summary":"Create a validation study and freeze A/B forms (moderator only)","description":"Independent human evidence is initially empty. Protocol and scopes are frozen. Separate from production qualification.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","protocol_version","cohort","specializations","grades","retest_min_days","retest_max_days"],"properties":{"name":{"type":"string","minLength":3,"maxLength":120},"protocol_version":{"type":"string"},"cohort":{"type":"string"},"specializations":{"type":"array","items":{"type":"string","enum":["Backend","Frontend"]}},"grades":{"type":"array","items":{"type":"string","enum":["Junior","Middle","Senior"]}},"retest_min_days":{"type":"integer","minimum":1,"maximum":90},"retest_max_days":{"type":"integer","minimum":1,"maximum":90}}}}}},"responses":{"200":{"description":"Role-filtered study projection; never stored answer keys"},"201":{"description":"Created study ID"},"400":{"description":"Invalid command or missing protocol prerequisites"},"401":{"description":"Cookie session required"},"403":{"description":"Study role does not permit this action"},"404":{"description":"Study absent or caller is not a member"},"409":{"description":"Immutable evidence, closed study or concurrent revision conflict"},"428":{"description":"MFA required for privileged accounts"},"429":{"description":"Rate limit reached"}},"security":[{"freehireSession":[]}]}},"/api/v1/fsp/validation-studies/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Read a study through its coordinator, blind-expert or participant projection","responses":{"200":{"description":"Role-filtered study projection; never stored answer keys"},"400":{"description":"Invalid command or missing protocol prerequisites"},"401":{"description":"Cookie session required"},"403":{"description":"Study role does not permit this action"},"404":{"description":"Study absent or caller is not a member"},"409":{"description":"Immutable evidence, closed study or concurrent revision conflict"},"428":{"description":"MFA required for privileged accounts"},"429":{"description":"Rate limit reached"}},"security":[{"freehireSession":[]}]},"post":{"summary":"Apply a study command with optimistic revision concurrency control","description":"Identity comes from authenticated membership, never from submitted reviewer IDs. Recruiting accepts immutable human reviews and consensus; running accepts separate attempts; analysis and closed freeze evidence. Enroll is coordinator-only and maps an existing account to exactly one study role. Start/submit never write production qualifications.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["status","enroll","reference","consensus","content","assign","start","submit"]},"status":{"type":"string","enum":["recruiting","running","analysis","closed"]},"user_id":{"type":"integer"},"role":{"type":"string","enum":["expert","participant"]},"specialization":{"type":"string"},"grade":{"type":"string"},"participant_id":{"type":"string","format":"uuid"},"form_id":{"type":"string"},"attempt_id":{"type":"string","format":"uuid"},"confidence":{"type":"integer","minimum":1,"maximum":5},"source":{"type":"string"},"basis":{"type":"string","maxLength":2000},"independent":{"type":"boolean"},"content":{"type":"object","description":"form_id, question_id; five 1–5 ratings (specialization_relevance, grade_relevance, clarity, difficulty, professional_value); estimated_minutes; decision accept/revise/reject; optional comment"},"answers":{"type":"object","additionalProperties":{"type":"string"}},"feedback":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Role-filtered study projection; never stored answer keys"},"400":{"description":"Invalid command or missing protocol prerequisites"},"401":{"description":"Cookie session required"},"403":{"description":"Study role does not permit this action"},"404":{"description":"Study absent or caller is not a member"},"409":{"description":"Immutable evidence, closed study or concurrent revision conflict"},"428":{"description":"MFA required for privileged accounts"},"429":{"description":"Rate limit reached"}},"security":[{"freehireSession":[]}]}},"/api/v1/fsp/validation-studies/{id}/export":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Download a coordinator-only anonymized validation bundle in analysis/closed","description":"Five files: summary JSON, participant/item/expert CSVs and report Markdown. Omits keys, account mappings and free-text human notes. Random study-scoped codes are pseudonymous; small groups still require restricted handling.","responses":{"200":{"description":"ZIP research bundle","content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid command or missing protocol prerequisites"},"401":{"description":"Cookie session required"},"403":{"description":"Study role does not permit this action"},"404":{"description":"Study absent or caller is not a member"},"409":{"description":"Immutable evidence, closed study or concurrent revision conflict"},"428":{"description":"MFA required for privileged accounts"},"429":{"description":"Rate limit reached"}},"security":[{"freehireSession":[]}]}},"/api/v1/fsp/employer-profile":{"get":{"summary":"Read own structured employer facts, provenance and effective verification","description":"Cookie/MFA, same-origin writes, active company owner, explicit confirmation, bounded validation and existing privacy audit. New flows require their feature flag.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}}},"put":{"summary":"Confirm employer facts with optimistic version; cannot set verification","description":"Cookie/MFA, same-origin writes, active company owner, explicit confirmation, bounded validation and existing privacy audit. New flows require their feature flag.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/engineering-teams":{"get":{"summary":"List up to 100 engineering teams owned by the active company account","description":"Cookie/MFA, same-origin writes, active company owner, explicit confirmation, bounded validation and existing privacy audit. New flows require their feature flag.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}}},"post":{"summary":"Create confirmed engineering context, distinct from access membership","description":"Cookie/MFA, same-origin writes, active company owner, explicit confirmation, bounded validation and existing privacy audit. New flows require their feature flag.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}}}},"/api/v1/fsp/engineering-teams/{id}":{"put":{"summary":"Update owned engineering context with optimistic version","description":"Cookie/MFA, same-origin writes, active company owner, explicit confirmation, bounded validation and existing privacy audit. New flows require their feature flag.","responses":{"200":{"description":"Successful response in data envelope"},"400":{"description":"Invalid fields or unsupported criterion"},"401":{"description":"Session required"},"403":{"description":"Origin or role denied"},"404":{"description":"Record absent or inaccessible"},"409":{"description":"Stale version or prerequisite missing"},"428":{"description":"MFA required"},"429":{"description":"Rate limited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/v1/fsp/integrations/fsp/status":{"get":{"summary":"Read own federation connection status","description":"Returns link/sync state, source, last synchronization, bounded counts and safe error codes. No caller-selected candidate identity.","responses":{"200":{"description":"Owner-scoped result; no federation participant identifier in public responses"},"400":{"description":"Malformed or unknown request fields"},"401":{"description":"Cookie authentication required"},"409":{"description":"Stale revision, missing profile/link or concurrent synchronization"},"429":{"description":"Distributed request limit"},"503":{"description":"Disabled capability or unavailable provider; previous facts retained"}}}},"/api/v1/fsp/integrations/fsp/link":{"post":{"summary":"Explicitly link a federation participant","description":"Requires consent and provider ownership proof. Only the mock provider is configured; the demo participant is not real identity verification.","responses":{"200":{"description":"Owner-scoped result; no federation participant identifier in public responses"},"400":{"description":"Malformed or unknown request fields"},"401":{"description":"Cookie authentication required"},"409":{"description":"Stale revision, missing profile/link or concurrent synchronization"},"429":{"description":"Distributed request limit"},"503":{"description":"Disabled capability or unavailable provider; previous facts retained"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"participantId":{"type":"string","maxLength":120},"consent":{"type":"boolean","enum":[true]}},"required":["participantId","consent"]}}}}},"delete":{"summary":"Revoke own link and hide imported evidence","description":"Cancels any pending synchronization token. Management remains available when imports are disabled.","responses":{"200":{"description":"Owner-scoped result; no federation participant identifier in public responses"},"400":{"description":"Malformed or unknown request fields"},"401":{"description":"Cookie authentication required"},"409":{"description":"Stale revision, missing profile/link or concurrent synchronization"},"429":{"description":"Distributed request limit"},"503":{"description":"Disabled capability or unavailable provider; previous facts retained"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{},"required":[]}}}}}},"/api/v1/fsp/integrations/fsp/sync":{"post":{"summary":"Synchronize normalized federation facts","description":"Server-side provider only; no submitted evidence payload. Idempotent source keys, five-second provider deadline, bounded lease, no automatic retries. Mock records cannot become verified.","responses":{"200":{"description":"Owner-scoped result; no federation participant identifier in public responses"},"400":{"description":"Malformed or unknown request fields"},"401":{"description":"Cookie authentication required"},"409":{"description":"Stale revision, missing profile/link or concurrent synchronization"},"429":{"description":"Distributed request limit"},"503":{"description":"Disabled capability or unavailable provider; previous facts retained"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{},"required":[]}}}}}},"/api/v1/fsp/integrations/fsp/evidence/{id}":{"put":{"summary":"Hide, share or report an imported fact","description":"Owner-only optimistic revision. Imported values cannot be manually edited. Reports hide the record and suspend verification; synchronization does not clear reports.","responses":{"200":{"description":"Owner-scoped result; no federation participant identifier in public responses"},"400":{"description":"Malformed or unknown request fields"},"401":{"description":"Cookie authentication required"},"409":{"description":"Stale revision, missing profile/link or concurrent synchronization"},"429":{"description":"Distributed request limit"},"503":{"description":"Disabled capability or unavailable provider; previous facts retained"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"version":{"type":"integer","minimum":1},"visible":{"type":"boolean"},"report":{"type":"boolean"}},"required":["version"]}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}},"/api/v1/fsp/qualification/catalog":{"get":{"summary":"List generated assessment blueprints and skill coverage","description":"Cookie-authenticated catalog derived from the current generator. Includes six role/grade blueprints and topics, version, duration, pass score and expert-validation status. No prompts or answer keys. Availability does not imply independent validation.","responses":{"200":{"description":"Catalog entries in a data array"},"401":{"description":"Authentication required"}}}},"/api/v1/fsp/profile-ai":{"get":{"summary":"Preview own canonical skills and years before external profile generation","responses":{"200":{"description":"Safe facts and saved profile revision; no provider call"},"401":{"description":"Cookie session required"},"503":{"description":"Feature disabled"}}},"post":{"summary":"Generate a consented profile draft; never saves profile fields","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["consent","revision"],"properties":{"consent":{"type":"boolean","enum":[true]},"revision":{"type":"string","minLength":64,"maxLength":64}}}}}},"responses":{"200":{"description":"Validated about draft and questions; human confirmation required"},"400":{"description":"Consent missing or invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin request required"},"409":{"description":"Saved profile changed since consent preview"},"422":{"description":"No supported skills"},"429":{"description":"Request limit"},"503":{"description":"Feature unavailable, budget exhausted or invalid provider response"}}}},"/fsp-api/profile-ai":{"get":{"summary":"Preview own canonical skills and years before external profile generation","responses":{"200":{"description":"Safe facts and saved profile revision; no provider call"},"401":{"description":"Cookie session required"},"503":{"description":"Feature disabled"}}},"post":{"summary":"Generate a consented profile draft; never saves profile fields","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["consent","revision"],"properties":{"consent":{"type":"boolean","enum":[true]},"revision":{"type":"string","minLength":64,"maxLength":64}}}}}},"responses":{"200":{"description":"Validated about draft and questions; human confirmation required"},"400":{"description":"Consent missing or invalid input"},"401":{"description":"Cookie session required"},"403":{"description":"Same-origin request required"},"409":{"description":"Saved profile changed since consent preview"},"422":{"description":"No supported skills"},"429":{"description":"Request limit"},"503":{"description":"Feature unavailable, budget exhausted or invalid provider response"}}}},"/api/v1/fsp/assistant-ai":{"post":{"summary":"Preview or generate consented search/document extraction or skill explanations","description":"Cookie session and same-origin JSON required. Preview masks contacts locally; generate requires explicit consent to the reviewed text. Candidate search and vacancy extraction require a verified organization. No automatic profile changes or ranking changes. Model facts require allowed values and verbatim source citations.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["purpose","target","stage","text","consent"],"properties":{"purpose":{"type":"string","enum":["search","resume","vacancy","explain"]},"target":{"type":"string","enum":["jobs","candidates"]},"stage":{"type":"string","enum":["preview","generate"]},"text":{"type":"string","maxLength":10000},"consent":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Reviewed text or validated proposal; proxy adds filters or saved import preview"},"400":{"description":"Invalid input, missing consent or unreviewed contacts"},"401":{"description":"Session required"},"403":{"description":"Same origin or verified organization required"},"429":{"description":"Request limit"},"503":{"description":"Feature disabled, budget exhausted, provider failure or rejected output"}}}},"/fsp-api/ai":{"post":{"summary":"Preview or generate consented search/document extraction or skill explanations","description":"Cookie session and same-origin JSON required. Preview masks contacts locally; generate requires explicit consent to the reviewed text. Candidate search and vacancy extraction require a verified organization. No automatic profile changes or ranking changes. Model facts require allowed values and verbatim source citations.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["purpose","target","stage","text","consent"],"properties":{"purpose":{"type":"string","enum":["search","resume","vacancy","explain"]},"target":{"type":"string","enum":["jobs","candidates"]},"stage":{"type":"string","enum":["preview","generate"]},"text":{"type":"string","maxLength":10000},"consent":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Reviewed text or validated proposal; proxy adds filters or saved import preview"},"400":{"description":"Invalid input, missing consent or unreviewed contacts"},"401":{"description":"Session required"},"403":{"description":"Same origin or verified organization required"},"429":{"description":"Request limit"},"503":{"description":"Feature disabled, budget exhausted, provider failure or rejected output"}}}},"/fsp-api/vacancy-ai":{"post":{"summary":"Parse a consented vacancy or specialist query for the existing catalog","description":"Requires an authenticated same-origin request and explicit consent. Locally masks contacts before calling the scoped provider. Returns canonical filters; no separate result list or confirmation step. Candidate searches require a verified organization and return filters for the authorized specialist catalog.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text","consent"],"properties":{"text":{"type":"string","minLength":3,"maxLength":2000},"consent":{"type":"boolean","enum":[true]},"target":{"type":"string","enum":["jobs","candidates"],"default":"jobs"}}}}}},"responses":{"200":{"description":"Canonical filters"},"400":{"description":"Invalid query or missing consent"},"401":{"description":"Session required"},"422":{"description":"No usable filters or unsupported condition"},"429":{"description":"Rate limit"},"503":{"description":"AI unavailable"}}}},"/api/v1/employer/jobs":{"get":{"summary":"List owned vacancies, including incomplete and closed records","security":[{"freehireSession":[]}],"responses":{"200":{"description":"Owned vacancies"},"401":{"description":"Session required"}}},"post":{"summary":"Create an employer vacancy","description":"FSP mode requires a verified organization and both salary bounds, minimum <= maximum, RUB per month. Changes invalidate the current moderation revision. Ownership is enforced.","security":[{"freehireSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["salary_min","salary_max","salary_currency","salary_period","url","title"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"location":{"type":"string"},"remote":{"type":"boolean"},"salary_min":{"type":"integer","minimum":1,"maximum":10000000},"salary_max":{"type":"integer","minimum":1,"maximum":10000000},"salary_currency":{"type":"string","enum":["RUB"]},"salary_period":{"type":"string","enum":["month"]},"url":{"type":"string","format":"uri"}}}}}},"responses":{"201":{"description":"Saved vacancy"},"400":{"description":"Invalid body or missing/invalid salary range"},"401":{"description":"Session required"},"403":{"description":"Verified organization required"},"404":{"description":"Vacancy not found or not owned"}}}},"/api/v1/employer/jobs/{slug}":{"parameters":[{"in":"path","name":"slug","required":true,"schema":{"type":"string"}}],"patch":{"summary":"Edit an owned employer vacancy and its salary","description":"FSP mode requires a verified organization and both salary bounds, minimum <= maximum, RUB per month. Changes invalidate the current moderation revision. Ownership is enforced.","security":[{"freehireSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["salary_min","salary_max","salary_currency","salary_period"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"location":{"type":"string"},"remote":{"type":"boolean"},"salary_min":{"type":"integer","minimum":1,"maximum":10000000},"salary_max":{"type":"integer","minimum":1,"maximum":10000000},"salary_currency":{"type":"string","enum":["RUB"]},"salary_period":{"type":"string","enum":["month"]}}}}}},"responses":{"200":{"description":"Saved vacancy"},"400":{"description":"Invalid body or missing/invalid salary range"},"401":{"description":"Session required"},"403":{"description":"Verified organization required"},"404":{"description":"Vacancy not found or not owned"}}}},"/api/v1/auth/oauth/fsp-id/start":{"get":{"summary":"Start FSP ID OIDC browser login","parameters":[{"name":"returnTo","in":"query","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to configured issuer with PKCE and nonce"},"404":{"description":"Provider disabled"},"503":{"description":"Issuer unavailable"}}}},"/api/v1/auth/oauth/fsp-id/callback":{"get":{"summary":"Consume cookie-bound OIDC callback once","responses":{"302":{"description":"Local session, MFA completion page, or sign-in error"}}}},"/api/v1/auth/fsp-id/mfa":{"post":{"summary":"Complete local second factor after FSP ID login","description":"Requires a short-lived HttpOnly pending-login cookie and same-origin JSON request. Five attempts, ten-minute lifetime, one successful redemption.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","maxLength":32}}}}}},"responses":{"200":{"description":"Session issued; data.returnTo is a safe local path"},"401":{"description":"Invalid or expired challenge/code"},"429":{"description":"Rate limited"}}}},"/api/v1/fsp/qualification/survey":{"put":{"summary":"Save the authenticated candidate qualification survey","description":"Profile required. Experience is self-declared and never certifies a grade. Active attempts lock survey edits; a locked confirmed category cannot be changed. Historic confirmations are preserved without inventing survey answers. Same-origin JSON and cookie authentication required.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["industry","specialization","grade","experienceBand"],"properties":{"industry":{"type":"string","enum":["IT"]},"specialization":{"type":"string","enum":["Backend","Frontend"]},"grade":{"type":"string","enum":["Junior","Middle","Senior"]},"experienceBand":{"type":"string","enum":["none","under_one","one_to_three","over_three"]}}}}}},"responses":{"200":{"description":"Owner-scoped qualification, attempt history and active public questions; no answer keys"},"400":{"description":"Invalid specialization, grade or answers"},"401":{"description":"Cookie session required"},"404":{"description":"Attempt not owned by caller"},"409":{"description":"Profile missing, cooldown active, or attempt expired"},"429":{"description":"Request limit reached"}}}},"/api/v1/fsp/triage-questions":{"get":{"summary":"Read own clarification inbox or employer vacancy questions","description":"Cookie session; private no-store. Without job: candidate inbox, including employer-first questions. With job: current authorized employer vacancy scope. limit/offset pagination.","parameters":[{"name":"job","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Question records and pagination metadata"},"401":{"description":"Authentication required"}}},"post":{"summary":"Request candidate clarification","description":"Verified employer with MFA. Empty slug: employer-first canonical skill questions, no application needed; candidate visibility rechecked. Nonempty slug: existing application-bound vacancy workflow. Questions are idempotent, candidate-declared and never confer qualification.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["handles","questions"],"properties":{"slug":{"type":"string"},"handles":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"string"}},"questions":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"object","required":["key","question"],"properties":{"key":{"type":"string"},"question":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"data.created count; zero for duplicate or inaccessible candidates"},"400":{"description":"Invalid question"},"403":{"description":"Employer access denied"}}}},"/api/v1/fsp/triage-questions/{id}":{"put":{"summary":"Answer own clarification once","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","enum":["yes","no","unknown"]},"detail":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Answer saved separately from profile; next search recomputes matching"},"400":{"description":"Invalid answer"},"409":{"description":"Already answered or inaccessible"}}}}},"components":{"securitySchemes":{"freehireSession":{"type":"apiKey","in":"cookie","name":"hire_token"}}},"security":[{"freehireSession":[]}]}