openapi: 3.1.0 info: title: missionMATCH API version: "1.0" description: > Cleared Workforce and Proposal Staffing Intelligence platform API. Every /api route is multi tenant (Postgres RLS scoped to the validated session org), behind a first party opaque server side session plus a signed double submit CSRF token, with per route role based access control and an append only audit trail. Scores come from one deterministic decimal engine. Clearance is always a self reported indicator, never platform verified. This document is the API contract surface; the rich intelligence view bodies are summarized as objects rather than fully expanded. servers: - url: https://screenmatch.thatwebhostingguy.com description: pilot - url: http://localhost:8080 description: local tags: - name: health - name: auth - name: candidates - name: requirements - name: matching - name: slates - name: missioncheck - name: commandcenter - name: market - name: sourcing - name: users - name: compliance - name: staffing - name: brain components: securitySchemes: cookieSession: type: apiKey in: cookie name: __Host-mm_session description: Opaque server side session, two stage (pre MFA then active), HttpOnly Secure SameSite Strict. csrfToken: type: apiKey in: header name: X-CSRF-Token description: Signed double submit token from GET /api/auth/csrf, required on every unsafe verb. schemas: Error: type: object properties: error: type: string CandidateListView: type: object properties: id: type: string format: uuid full_name: type: string clearance_level: type: string description: DB code unknown none confidential secret top_secret or ts_sci clearance_status: type: string years_experience: type: integer location: type: string available: type: boolean RequirementListView: type: object properties: id: type: string format: uuid title: type: string required_level: type: string customer_agency: type: string key_personnel: type: boolean MatchResultView: type: object description: The explainable score (decimal values as fixed strings) plus the per component trace. properties: candidate_id: type: string requirement_id: type: string total_score: type: string example: "92.00" band: type: string enum: [green, yellow, red] explanation: type: string gap_summary: type: array items: type: string ScreenView: type: object description: > Full screenMATCH intelligence. Contains match, mission_readiness, battle_card, clearance_intelligence, contract_readiness, win_impact, and next_steps. MissionCheckRequest: type: object properties: resume_text: type: string description: Resume text extracted in the browser. The file is never uploaded. job_description_text: type: string description: Mode B only. limit: type: integer description: Mode A only. SaveCandidateRequest: type: object required: [full_name] properties: full_name: type: string clearance_level: type: string clearance_poly: type: string clearance_status: type: string years_experience: type: integer location: type: string skills: type: array items: type: string certifications: type: array items: type: string customer_experience: type: array items: type: string LoginRequest: type: object required: [email, password] properties: email: type: string format: email password: type: string StaffingError: type: object description: The one typed error envelope for the W3 staffing endpoints. properties: error: type: object required: [code, message] properties: code: type: string description: validation | not_found | conflict | problem_too_large | solve_timeout | no_database | no_tenant | bad_request | internal message: { type: string } fields: type: object additionalProperties: { type: string } description: Present on a 422; per field validation messages. PortfolioCounterfactual: type: object description: The per assignment explanation computed from the value matrix, not from solver internals. properties: assigned_total: { type: string, description: The assigned candidate engine total for this requirement (0 to 100). } candidate_next_requirement: { type: string, format: uuid, description: The candidate's next best alternative requirement. } candidate_value_delta: { type: integer, format: int64, description: Scaled value lost by not placing them on their next best requirement. } requirement_next_candidate: { type: string, format: uuid, description: The requirement's next best candidate if this one were removed. } requirement_value_delta: { type: integer, format: int64, description: Scaled value drop to that next best candidate. } RecommendationEvidence: type: object description: Structured justification; only the fields relevant to the action are present. properties: engine_total: { type: string } band: { type: string, enum: [green, yellow, red] } reskill: type: object properties: held_skill: { type: string } missing_skill: { type: string } distance: { type: string, description: Cosine distance between the held and the missing skill. } reassign: type: object properties: from_requirement_id: { type: string, format: uuid } priority_delta: { type: string } counterfactual: { $ref: '#/components/schemas/PortfolioCounterfactual' } open_slots: { type: integer, description: On a source recommendation, the slots with no qualified, available bench. } trigger: type: object properties: kind: { type: string, description: availability | certification | clearance | new_opportunity | recompute } detected_at: { type: string, format: date-time } faith_fallback_reason: type: string description: > Why the faithfulness gate did not confirm the prose (could_not_confirm, plane_timeout, circuit_open, generation_disabled, proactive_batch, eeo_guard). Recorded for narration tuning; never in an audit payload. StaffingRecommendation: type: object description: One recommended staffing action. The decision columns mirror slate_members. required: [id, run_id, requirement_id, action, rank, confidence, rationale, rationale_source, status] properties: id: { type: string, format: uuid } run_id: { type: string, format: uuid } requirement_id: { type: string, format: uuid } candidate_id: { type: string, format: uuid, description: Absent for a source recommendation, which names no candidate. } action: { type: string, enum: [assign, reassign, reskill, source, team] } rank: { type: integer } confidence: { type: string, description: Deterministic transparency aid in [0,1], never a probability and never a decision input. } value_score: { type: integer, format: int64, description: Scaled optimizer objective value; portfolio runs only. } rationale: { type: string } rationale_source: { type: string, enum: [ai_faithful, deterministic] } evidence: { $ref: '#/components/schemas/RecommendationEvidence' } status: { type: string, enum: [pending, approved, rejected, dismissed, superseded] } decided_by: { type: string, format: uuid } decided_at: { type: string, format: date-time } decision_note: { type: string } created_at: { type: string, format: date-time } StaffingRun: type: object required: [id, view, model_version, parameters, created_at] properties: id: { type: string, format: uuid } view: { type: string, enum: [gap, portfolio, proactive] } scope_ref: { type: string, format: uuid, description: Requirement id for a gap run, candidate id for a proactive run, absent for portfolio. } model_version: { type: string } parameters: { type: object, description: The staffing floor, adjacency threshold, value reference, scale, and value source actually used. } created_at: { type: string, format: date-time } StaffingRunResponse: type: object required: [run, recommendations] properties: run: { $ref: '#/components/schemas/StaffingRun' } recommendations: type: array items: { $ref: '#/components/schemas/StaffingRecommendation' } ProactiveResponse: type: object properties: recommendations: type: array items: { $ref: '#/components/schemas/StaffingRecommendation' } limit: { type: integer } offset: { type: integer } GapRequest: type: object description: Optional per run overrides; the values used are recorded in the run parameters. properties: staffing_floor: { type: string, description: "decimal in [0,100]; default 75, the Yellow band floor" } adjacency_threshold: { type: string, description: "decimal cosine distance in [0,2]; default 0.40" } PortfolioRequest: type: object properties: requirement_ids: type: array items: { type: string, format: uuid } description: Optional explicit subset of open requirements; when omitted, ALL open requirements are used. staffing_floor: { type: string } adjacency_threshold: { type: string } DecisionRequest: type: object required: [decision] properties: decision: { type: string, enum: [approved, rejected, dismissed] } note: { type: string } BrainError: type: object description: The one typed error envelope for the brain endpoint. A raw SQL or database error is never returned. properties: error: type: object required: [code, message] properties: code: { type: string, description: "brain_unavailable | plane_unavailable | vector_model_mismatch | unknown_intent | refused | no_tenant | validation | bad_request | brain_error" } message: { type: string } BrainQueryRequest: type: object description: > A natural language question routes across CAG (stable rules), RAG (retrieve and narrate), and a refusal. An explicit intent runs the curated, parameterized, read only TAG path over the database of record. The brain NEVER authors free form SQL; an exact aggregation over tenant data uses an intent. properties: question: { type: string, description: A natural language question. Required unless an intent is given. } intent: { type: string, description: "Optional curated TAG intent name (for example count_candidates_by_clearance)." } args: type: array items: {} description: Positional arguments for the intent, validated against the intent's parameter spec. BrainAnswer: type: object description: > The brain's answer plus its provenance. rationale_source is ai_faithful when the model prose passed the faithfulness gate, or deterministic when it fell back to the rendering of the evidence (still a 200). citations are the evidence the answer was grounded on; path is the augmented strategy that served it. The deterministic engine remains the floor; the brain narrates, it never scores. required: [answer, path, rationale_source, citations] properties: answer: { type: string } path: { type: string, enum: [cag, tag, rag] } rationale_source: { type: string, enum: [ai_faithful, deterministic] } citations: type: array items: { type: string } faithful: { type: boolean } evidence_count: { type: integer } fallback_reason: { type: string, description: "Why the deterministic fallback was used (no_model, circuit_open, plane_timeout, could_not_confirm). Never in the audit payload." } responses: Forbidden: description: The session role lacks the required capability, or no validated tenant. content: application/json: schema: $ref: '#/components/schemas/Error' BadRequest: description: Invalid input. content: application/json: schema: $ref: '#/components/schemas/Error' security: - cookieSession: [] csrfToken: [] paths: /healthz: get: tags: [health] summary: Liveness. Never touches the database. security: [] responses: '200': description: ok /readyz: get: tags: [health] summary: Readiness. Pings the app and compliance pools. security: [] responses: '200': description: ready '503': description: a pool is not ready /healthz/fips: get: tags: [health] summary: FIPS module status. security: [] responses: '200': description: module info /metrics: get: tags: [health] summary: Prometheus metrics. Operational, no PII. security: [] responses: '200': description: exposition text /api/auth/csrf: get: tags: [auth] summary: Issue the CSRF double submit token and set the companion cookie. security: [] responses: '200': description: the csrf_token /api/auth/login: post: tags: [auth] summary: Password login stage 1. Returns a pre MFA session. MFA is mandatory before an active session. security: - csrfToken: [] requestBody: content: application/json: schema: $ref: '#/components/schemas/LoginRequest' responses: '200': description: pre MFA session established '401': description: bad credentials '429': description: rate limited /api/auth/mfa/enroll: post: tags: [auth] summary: Begin TOTP enrollment. Returns the otpauth uri and recovery codes. security: - csrfToken: [] responses: '200': description: enrollment material /api/auth/mfa/activate: post: tags: [auth] summary: Activate TOTP by confirming a code during enrollment. security: - csrfToken: [] responses: '200': description: activated /api/auth/mfa/verify: post: tags: [auth] summary: Verify a TOTP code to upgrade a pre MFA session to active. security: - csrfToken: [] responses: '200': description: active session '401': description: bad code /api/auth/mfa/recover: post: tags: [auth] summary: Use a single use recovery code in place of TOTP. security: - csrfToken: [] responses: '200': description: active session /api/auth/invite/accept: post: tags: [auth] summary: Accept an invite. Set the password and enroll MFA. security: - csrfToken: [] responses: '200': description: accepted /api/auth/session: get: tags: [auth] summary: The current validated principal (org, user, role). responses: '200': description: session principal '403': $ref: '#/components/responses/Forbidden' /api/auth/logout: post: tags: [auth] summary: Revoke the current session. responses: '200': description: logged out /api/auth/logout-all: post: tags: [auth] summary: Revoke every session for the current user. responses: '200': description: done /api/auth/password/change: post: tags: [auth] summary: Change the password for the current user. responses: '200': description: changed /api/candidates: get: tags: [candidates] summary: List candidates. Capability candidate.read. parameters: - name: limit in: query schema: type: integer - name: offset in: query schema: type: integer responses: '200': description: candidate list content: application/json: schema: type: array items: $ref: '#/components/schemas/CandidateListView' '403': $ref: '#/components/responses/Forbidden' post: tags: [candidates] summary: Create a candidate. Capability candidate.write, audited. Clearance is self reported. requestBody: content: application/json: schema: $ref: '#/components/schemas/SaveCandidateRequest' responses: '201': description: the new id '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' /api/candidates/{candidateID}: get: tags: [candidates] summary: One candidate by id. Capability candidate.read. Missing or cross org both return 404. parameters: - name: candidateID in: path required: true schema: type: string format: uuid responses: '200': description: candidate content: application/json: schema: $ref: '#/components/schemas/CandidateListView' '404': description: not found /api/candidates/intelligence: get: tags: [candidates] summary: Workforce intelligence view. Scores the pool. Capability search.run, audited served scores. responses: '200': description: candidate intelligence list /api/candidates/{candidateID}/opportunities: get: tags: [candidates] summary: Best fit open opportunities for one candidate. Capability search.run. parameters: - name: candidateID in: path required: true schema: type: string format: uuid responses: '200': description: ranked opportunity fits /api/candidates/{candidateID}/clearance-provenance: patch: tags: [candidates] summary: Record how a clearance is known. Capability candidate.write, audited. The platform never verifies a clearance. parameters: - name: candidateID in: path required: true schema: type: string format: uuid requestBody: content: application/json: schema: type: object properties: provenance: type: string enum: [self_reported, sponsor_verified, document, continuous_vetting] verified_at: type: string format: date responses: '200': description: updated '400': $ref: '#/components/responses/BadRequest' '404': description: not found /api/candidates/{candidateID}/notice: post: tags: [compliance] summary: Record an AEDT candidate notice and disclosure. Capability candidate.write, audited. parameters: - name: candidateID in: path required: true schema: type: string format: uuid responses: '200': description: recorded /api/requirements: get: tags: [requirements] summary: List requirements. Capability candidate.read. responses: '200': description: requirement list content: application/json: schema: type: array items: $ref: '#/components/schemas/RequirementListView' /api/requirements/{requirementID}: get: tags: [requirements] summary: One requirement by id. Capability candidate.read. parameters: - name: requirementID in: path required: true schema: type: string format: uuid responses: '200': description: requirement content: application/json: schema: $ref: '#/components/schemas/RequirementListView' '404': description: not found /api/requirements/{requirementID}/candidates: get: tags: [matching] summary: Rank every candidate against one requirement. Capability search.run, audited served scores. parameters: - name: requirementID in: path required: true schema: type: string format: uuid responses: '200': description: ranked candidates /api/match: post: tags: [matching] summary: screenMATCH score one candidate vs one requirement. Capability search.run, audited. requestBody: content: application/json: schema: type: object properties: candidate_id: type: string requirement_id: type: string responses: '200': description: match result content: application/json: schema: $ref: '#/components/schemas/MatchResultView' /api/screen: post: tags: [matching] summary: Full screening intelligence for one candidate vs one requirement. Capability search.run, audited. requestBody: content: application/json: schema: type: object properties: candidate_id: type: string requirement_id: type: string responses: '200': description: screen view content: application/json: schema: $ref: '#/components/schemas/ScreenView' /api/slates/generate: post: tags: [slates] summary: Generate and freeze a candidate slate. Capability slate.write, audited. responses: '201': description: slate result /api/slates/{slateID}/members/{candidateID}/decision: patch: tags: [slates] summary: Human in the loop approve or reject a slate member. Capability slate.write, audited. Never auto rejects. parameters: - name: slateID in: path required: true schema: type: string format: uuid - name: candidateID in: path required: true schema: type: string format: uuid requestBody: content: application/json: schema: type: object properties: decision: type: string enum: [approved, rejected] responses: '200': description: recorded /api/slates/{slateID}/members/{candidateID}/trace: get: tags: [slates] summary: The frozen decision trace for a slate member. Capability candidate.read. parameters: - name: slateID in: path required: true schema: type: string format: uuid - name: candidateID in: path required: true schema: type: string format: uuid responses: '200': description: decision trace /api/mission-check/opportunities: post: tags: [missioncheck] summary: Mode A. Score an in memory resume against every open opportunity. Capability search.run, ephemeral audit, stores nothing. requestBody: content: application/json: schema: $ref: '#/components/schemas/MissionCheckRequest' responses: '200': description: ranked rows plus the parsed candidate, assumptions, and data categories '400': $ref: '#/components/responses/BadRequest' /api/mission-check/screen: post: tags: [missioncheck] summary: Mode B. Score an in memory resume against a pasted job description. Capability search.run, ephemeral audit, stores nothing. requestBody: content: application/json: schema: $ref: '#/components/schemas/MissionCheckRequest' responses: '200': description: screen view, recommendation, parsed candidate and requirement '400': $ref: '#/components/responses/BadRequest' /api/command-center: get: tags: [commandcenter] summary: Workforce Command Center aggregate. Readiness, gaps, Revenue at Risk. Capability candidate.read. responses: '200': description: command center view /api/opportunities: get: tags: [market] summary: Cached SAM.gov opportunities, the requirement feed. responses: '200': description: opportunities /api/opportunities/{noticeID}: get: tags: [market] summary: One cached SAM.gov opportunity. parameters: - name: noticeID in: path required: true schema: type: string responses: '200': description: opportunity '404': description: not found /api/awards: get: tags: [market] summary: Cached USASpending incumbent awards by NAICS. responses: '200': description: awards /api/awards/galapagos: get: tags: [market] summary: Cached Galapagos past performance awards. responses: '200': description: awards /api/wages: get: tags: [market] summary: Cached BLS OEWS regional wage baseline. responses: '200': description: wages /api/sourcing/candidates: get: tags: [sourcing] summary: Externally sourced candidates. Capability sourcing.read. responses: '200': description: sourced candidates /api/sourcing/log: get: tags: [sourcing] summary: The sourcing provenance and legal basis log. Capability sourcing.read. responses: '200': description: sourcing log /api/sourcing/candidates/{id}: delete: tags: [sourcing] summary: Hard delete a sourced person and their PII, right to erasure. Capability privacy.erase, audited. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: erased /api/users: get: tags: [users] summary: List org users. Capability user.invite. responses: '200': description: user summaries /api/users/invite: post: tags: [users] summary: Invite a user with an authority bounded role. Capability user.invite, audited. The invite link is returned for manual delivery. responses: '200': description: invite link /api/users/{id}/role: patch: tags: [users] summary: Change a user role, strictly below the actor role. Capability user.role.change, audited. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: changed /api/users/{id}/status: patch: tags: [users] summary: Enable or disable a user. Capability user.disable, audited. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: changed /api/users/{id}/mfa/reset: post: tags: [users] summary: Reset a user MFA enrollment. Capability user.disable, audited. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: reset /api/users/{id}/recovery: post: tags: [users] summary: Reissue recovery material for a user. Capability user.invite, audited. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: recovery link /api/aedt/model-versions: get: tags: [compliance] summary: The AEDT model version registry. Capability compliance.read. responses: '200': description: model versions /api/aedt/inventory: get: tags: [compliance] summary: The AEDT tool inventory. Capability compliance.read. responses: '200': description: inventory post: tags: [compliance] summary: Upsert an AEDT inventory entry. Capability compliance.read, audited. responses: '200': description: upserted /api/compliance/retention/run: post: tags: [compliance] summary: Run the retention sweep, anonymize in place past the window. Capability compliance.read, audited. responses: '200': description: swept /api/compliance/adverse-impact: get: tags: [compliance] summary: The four fifths adverse impact report. Compliance role only. responses: '200': description: report '503': description: compliance pool not configured /api/compliance/adverse-impact/export: get: tags: [compliance] summary: Publication ready adverse impact export. Compliance role only. responses: '200': description: export text /api/audit: get: tags: [compliance] summary: The append only audit trail for the org. Capability audit.read. parameters: - name: limit in: query schema: type: integer - name: offset in: query schema: type: integer responses: '200': description: audit log /recommendations/gap/{requirementID}: post: tags: [staffing] summary: > Generate the ranked recommendations to close one named gap. Capability staffing.recommend (a write: it persists a fresh run, superseding prior pending gap rows for this requirement). Produces ASSIGN, REASSIGN, RESKILL, and a single SOURCE note; never auto rejects; the engine is the floor. parameters: - name: requirementID in: path required: true schema: { type: string, format: uuid } requestBody: required: false content: application/json: schema: { $ref: '#/components/schemas/GapRequest' } responses: '200': description: The run and its recommendations in rank order. content: application/json: schema: { $ref: '#/components/schemas/StaffingRunResponse' } '403': { $ref: '#/components/responses/Forbidden' } '404': description: The requirement is not a real, open gap in this tenant. content: { application/json: { schema: { $ref: '#/components/schemas/StaffingError' } } } '422': description: Invalid per run override (floor or adjacency out of range). content: { application/json: { schema: { $ref: '#/components/schemas/StaffingError' } } } /recommendations/portfolio: post: tags: [staffing] summary: > Optimize the whole available bench across the open requirements (a pure Go minimum cost flow that maximizes total value). Capability staffing.recommend. Each ASSIGN carries the scaled value and the value matrix counterfactual; unfilled slots become SOURCE. Two runs for one org serialize on an advisory lock. An empty bench or no open requirement returns an empty allocation (200), not an error. requestBody: required: false content: application/json: schema: { $ref: '#/components/schemas/PortfolioRequest' } responses: '200': description: The run and the optimal allocation with per assignment explanations. content: application/json: schema: { $ref: '#/components/schemas/StaffingRunResponse' } '403': { $ref: '#/components/responses/Forbidden' } '413': description: The candidate by requirement problem exceeds the configured size cap. content: { application/json: { schema: { $ref: '#/components/schemas/StaffingError' } } } '422': description: An unknown requirement id in the explicit subset, or an invalid override. content: { application/json: { schema: { $ref: '#/components/schemas/StaffingError' } } } '503': description: The portfolio solve exceeded its time budget. content: { application/json: { schema: { $ref: '#/components/schemas/StaffingError' } } } /recommendations/proactive: get: tags: [staffing] summary: > The current bench watch: the pending proactive recommendations the scheduled recompute wrote, per candidate best fits. Capability candidate.read. Capped and paginated. parameters: - name: limit in: query schema: { type: integer, description: Capped at the server read page maximum (default 200). } - name: offset in: query schema: { type: integer } responses: '200': description: The current bench watch. content: application/json: schema: { $ref: '#/components/schemas/ProactiveResponse' } '403': { $ref: '#/components/responses/Forbidden' } /recommendations/{recommendationID}/decision: post: tags: [staffing] summary: > Approve, reject, or dismiss one recommendation (human in the loop). Capability slate.write. The row is locked; a decision on a non pending row (already decided, or concurrently superseded) returns 409. Rejecting dismisses only that suggestion; it never blacklists a candidate, and nothing is automatic. parameters: - name: recommendationID in: path required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/DecisionRequest' } responses: '200': description: The decided recommendation. content: application/json: schema: { $ref: '#/components/schemas/StaffingRecommendation' } '403': { $ref: '#/components/responses/Forbidden' } '404': description: No such recommendation in this tenant. content: { application/json: { schema: { $ref: '#/components/schemas/StaffingError' } } } '409': description: The recommendation was already decided or superseded. content: { application/json: { schema: { $ref: '#/components/schemas/StaffingError' } } } '422': description: The decision is not one of approved, rejected, dismissed. content: { application/json: { schema: { $ref: '#/components/schemas/StaffingError' } } } /ai/query: post: tags: [brain] summary: > Ask the AI brain a natural language question. Capability brain.query (the search.run tier). The brain routes the question across CAG (stable rules), RAG (retrieve the tenant's own candidates and narrate), and a refusal, or runs a curated parameterized TAG intent over the database of record; it never authors free form SQL and never reaches the eeo schema. Every answer runs the faithfulness gate before it ships, with a deterministic fallback. Tenant isolated by RLS on the validated session org. The deterministic engine is the floor: any score the brain surfaces is the frozen evaluation, and no model is in any scoring or eligibility decision. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BrainQueryRequest' } responses: '200': description: > The answer. A faithfulness fallback is a normal 200 with rationale_source deterministic. A refused question (unsafe, demographic, unanswerable) is a 422, not a 200. content: application/json: schema: { $ref: '#/components/schemas/BrainAnswer' } '403': { $ref: '#/components/responses/Forbidden' } '422': description: A demographic or unsafe question (refused), an unknown intent, or a missing question and intent. content: { application/json: { schema: { $ref: '#/components/schemas/BrainError' } } } '503': description: > The inference plane is not configured or is temporarily unavailable (circuit open), or the stored vectors do not match the configured embed model (vector_model_mismatch, the brain is disabled until the store is re embedded). Never a hang. content: { application/json: { schema: { $ref: '#/components/schemas/BrainError' } } }