{"openapi":"3.1.0","info":{"title":"InterAnalysis B2B API","description":"\n## Interview Analysis Platform API with Database\n\nProfessional interview analysis service powered by AI and PostgreSQL database.\n\n### Key Features\n- 🎙️ **Audio Transcription** - High-accuracy transcription using AssemblyAI\n- 🗣️ **Speaker Diarization** - Automatic speaker identification\n- 🤖 **AI Analysis** - Comprehensive evaluation using GPT/Gemini\n- 💾 **PostgreSQL Storage** - Persistent data storage\n- 🔔 **Webhooks** - Real-time notifications\n- 🔐 **Multi-tenancy** - Secure tenant isolation\n\n### Database Status\nConnected to Railway PostgreSQL or in-memory fallback for local development.\n    ","version":"2.0.1"},"paths":{"/api/auth/register":{"post":{"tags":["Authentication"],"summary":"Register","description":"Register new organization and admin user\n\nCreates:\n- New organization (14-day trial)\n- Admin user account\n- Email verification token\n\nReturns access + refresh tokens for immediate login","operationId":"register_api_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"tags":["Authentication"],"summary":"Login","description":"Authenticate user and return tokens\n\nValidates credentials, checks user/org status, returns access + refresh tokens","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/lookup":{"post":{"tags":["Authentication"],"summary":"Sso Lookup","description":"Does this email's domain have SSO configured? (login-page probe).\n\nPublic and unauthenticated by design — it runs before login. Replaces the\ndirect-DB Next.js route (API proxy sweep); response shape and error\nstrings preserved verbatim. Only VERIFIED domains on ACTIVE orgs with an\nENABLED config answer true, so the probe can't be used to enumerate\nunverified/disabled tenant state.","operationId":"sso_lookup_api_auth_lookup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/logout":{"post":{"tags":["Authentication"],"summary":"Logout","description":"Logout user by revoking refresh token\n\nAccess tokens continue to work until expiry (15 min max)","operationId":"logout_api_auth_logout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh Access Token","description":"Exchange refresh token for new access token\n\nValidates refresh token and issues new access token","operationId":"refresh_access_token_api_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/me":{"get":{"tags":["Authentication"],"summary":"Get Current User Info","description":"Get current authenticated user information\n\nRequires valid access token in Authorization header","operationId":"get_current_user_info_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/auth/verify-email":{"post":{"tags":["Authentication"],"summary":"Verify Email","description":"Verify user email address\n\nUses token sent in verification email","operationId":"verify_email_api_auth_verify_email_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/send-verification-email":{"post":{"tags":["Authentication"],"summary":"Send Verification Email Endpoint","description":"Internal endpoint to send verification email.\nCalled by the Next.js API route after registration.","operationId":"send_verification_email_endpoint_api_auth_send_verification_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendVerificationEmailRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/resend-verification":{"post":{"tags":["Authentication"],"summary":"Resend Verification","description":"Re-send the signup verification link.\n\nThe post-signup screen's \"Resend verification email\" CTA. The existing\n/send-verification-email endpoint can't serve it — that one requires the\nverification TOKEN, which the browser never sees.\n\nEnumeration-safe, mirroring /password-reset/request: ALWAYS returns the same\nsuccess payload whether or not the address exists or is already verified, so\nan attacker can't use it to discover accounts. Rate-limited by the shared\nauth limiter so it can't be used to spam an inbox either. Mints a FRESH\ntoken (and expiry) rather than resending the old one, so a resend also\nrescues someone whose original link has expired.","operationId":"resend_verification_api_auth_resend_verification_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendVerificationRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/password-reset/request":{"post":{"tags":["Authentication"],"summary":"Request Password Reset","description":"Request password reset email\n\nAlways returns success (don't leak whether email exists)","operationId":"request_password_reset_api_auth_password_reset_request_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/password-reset/confirm":{"post":{"tags":["Authentication"],"summary":"Confirm Password Reset","description":"Reset password using token from email","operationId":"confirm_password_reset_api_auth_password_reset_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetConfirm"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/change-password":{"post":{"tags":["Authentication"],"summary":"Change Password","description":"Change password for the currently authenticated user.\nRequires current password verification.","operationId":"change_password_api_auth_change_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/calendar/connections":{"get":{"tags":["Calendar"],"summary":"List Calendar Connections","description":"List the current USER's calendar connection (per-user model, mig 112).\n\nConnections belong to the user who granted OAuth — the settings page\nshows \"your calendar\", never the org's pool.","operationId":"list_calendar_connections_api_v1_calendar_connections_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/calendar/connections/health":{"get":{"tags":["Calendar"],"summary":"Calendar Connection Health","description":"Live-verify the org's active calendar connection.\n\nGET /connections only reports the stored `is_active` flag — which stays\ntrue even after the OAuth token silently expires or is revoked, so the\nnew-interview page cheerfully shows \"Calendar connected\" while Meet\nauto-create is actually dead (the exact misleading-indicator bug this\nfixes). This endpoint instead calls get_credentials(), which attempts a\nreal token refresh and marks the connection inactive on RefreshError. So:\n  • healthy=true         → token is live, auto-create will work\n  • needs_reconnect=true → connection exists but its token is dead;\n                           the FE shows \"Calendar needs reconnecting\"\n  • connected=false      → no active connection at all\n\nKept cheap (credential refresh only, no events list) so it's fine to call\nonce on page load. The refresh persists a fresh access token as a side\neffect, so this doubles as passive token upkeep.","operationId":"calendar_connection_health_api_v1_calendar_connections_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/calendar/connect/google/url":{"get":{"tags":["Calendar"],"summary":"Get Google Oauth Url","description":"Get Google OAuth authorization URL","operationId":"get_google_oauth_url_api_v1_calendar_connect_google_url_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthUrlResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/calendar/connect/google/callback":{"get":{"tags":["Calendar"],"summary":"Google Oauth Callback","description":"Handle Google OAuth callback and redirect to frontend settings page","operationId":"google_oauth_callback_api_v1_calendar_connect_google_callback_get","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string","description":"Authorization code from Google","title":"Code"},"description":"Authorization code from Google"},{"name":"state","in":"query","required":true,"schema":{"type":"string","description":"State parameter (tenant_id:user_id)","title":"State"},"description":"State parameter (tenant_id:user_id)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/calendar/connections/{connection_id}/sync":{"post":{"tags":["Calendar"],"summary":"Sync Calendar Connection","description":"Manually trigger calendar sync for a connection","operationId":"sync_calendar_connection_api_v1_calendar_connections__connection_id__sync_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"connection_id","in":"path","required":true,"schema":{"type":"string","title":"Connection Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/calendar/connections/{connection_id}":{"delete":{"tags":["Calendar"],"summary":"Disconnect Calendar","description":"Disconnect (deactivate) a calendar connection","operationId":"disconnect_calendar_api_v1_calendar_connections__connection_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"connection_id","in":"path","required":true,"schema":{"type":"string","title":"Connection Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/calendar/meetings/upcoming":{"get":{"tags":["Calendar"],"summary":"Get Upcoming Meetings","description":"Get upcoming scheduled interview meetings","operationId":"get_upcoming_meetings_api_v1_calendar_meetings_upcoming_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","description":"Number of days to look ahead","default":7,"title":"Days"},"description":"Number of days to look ahead"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ScheduledMeetingResponse"},"title":"Response Get Upcoming Meetings Api V1 Calendar Meetings Upcoming Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/calendar/meetings/{meeting_id}":{"get":{"tags":["Calendar"],"summary":"Get Meeting Details","description":"Get detailed information about a scheduled meeting","operationId":"get_meeting_details_api_v1_calendar_meetings__meeting_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"meeting_id","in":"path","required":true,"schema":{"type":"string","title":"Meeting Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/calendar/meetings/{meeting_id}/trigger-bot":{"post":{"tags":["Calendar"],"summary":"Manually Trigger Bot","description":"Manually trigger bot for a meeting (bypass automatic scheduling)","operationId":"manually_trigger_bot_api_v1_calendar_meetings__meeting_id__trigger_bot_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"meeting_id","in":"path","required":true,"schema":{"type":"string","title":"Meeting Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/bots/active":{"get":{"tags":["Bots"],"summary":"Get Active Bots","description":"Get THIS org's currently active bots.\n\nThe orchestrator's active-bots map is process-wide (every tenant), so\nscope it to the caller by intersecting with bot_instances whose\nscheduled_meeting belongs to their tenant — otherwise any authenticated\nuser saw every org's running bots and their meeting URLs.","operationId":"get_active_bots_api_v1_bots_active_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/bots/instances":{"get":{"tags":["Bots"],"summary":"List Bot Instances","description":"List bot instances (historical and active)\n\nQuery parameters:\n- status: Filter by bot status (initializing, joining, joined, recording, completed, failed)\n- limit: Maximum number of results","operationId":"list_bot_instances_api_v1_bots_instances_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/bots/instances/{bot_instance_id}":{"get":{"tags":["Bots"],"summary":"Get Bot Instance","description":"Get detailed information about a specific bot instance","operationId":"get_bot_instance_api_v1_bots_instances__bot_instance_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bot_instance_id","in":"path","required":true,"schema":{"type":"string","title":"Bot Instance Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/bots/instances/{bot_instance_id}/stop":{"post":{"tags":["Bots"],"summary":"Stop Bot","description":"Emergency stop for a running bot\n\nThis will force the bot to leave the meeting immediately","operationId":"stop_bot_api_v1_bots_instances__bot_instance_id__stop_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bot_instance_id","in":"path","required":true,"schema":{"type":"string","title":"Bot Instance Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/bots/launch-manual":{"post":{"tags":["Bots"],"summary":"Launch Manual Bot","description":"Manually launch a bot for immediate testing (bypasses calendar scheduling)\n\nUse this for testing or manual intervention","operationId":"launch_manual_bot_api_v1_bots_launch_manual_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManualBotLaunchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/recordings/{interview_id}":{"get":{"tags":["recordings"],"summary":"Get Recording","description":"Stream/download recording file for authenticated user\n\nArgs:\n    interview_id: Interview UUID\n    current_user: Authenticated user (from JWT token)\n\nReturns:\n    Recording file as FileResponse\n\nRaises:\n    HTTPException: 404 if recording not found or user doesn't own interview","operationId":"get_recording_api_recordings__interview_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["recordings"],"summary":"Delete Recording","description":"Delete recording file (admin only or interview owner)\n\nArgs:\n    interview_id: Interview UUID\n    current_user: Authenticated user (from JWT token)\n\nReturns:\n    Success message\n\nRaises:\n    HTTPException: 404 if recording not found, 403 if unauthorized","operationId":"delete_recording_api_recordings__interview_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recordings/{interview_id}/info":{"get":{"tags":["recordings"],"summary":"Get Recording Info","description":"Get recording metadata without downloading the file\n\nArgs:\n    interview_id: Interview UUID\n    current_user: Authenticated user (from JWT token)\n\nReturns:\n    Dictionary with recording metadata\n\nRaises:\n    HTTPException: 404 if interview not found or user doesn't own it","operationId":"get_recording_info_api_recordings__interview_id__info_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recordings/bot/{bot_instance_id}":{"get":{"tags":["recordings"],"summary":"Download Bot Recording","description":"Download a meeting recording by bot instance ID\n\nVerifies:\n- User is authenticated\n- Recording belongs to user's tenant\n- Recording file exists\n\nReturns the .webm recording file as a download","operationId":"download_bot_recording_api_recordings_bot__bot_instance_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bot_instance_id","in":"path","required":true,"schema":{"type":"string","title":"Bot Instance Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recordings/bot/{bot_instance_id}/stream":{"get":{"tags":["recordings"],"summary":"Stream Bot Recording","description":"Stream or redirect to bot recording.\n\nHandles both:\n- Local files (Railway volume): Streams file directly\n- S3 URLs: Redirects to presigned URL for secure access","operationId":"stream_bot_recording_api_recordings_bot__bot_instance_id__stream_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bot_instance_id","in":"path","required":true,"schema":{"type":"string","title":"Bot Instance Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/extract-from-resume":{"post":{"tags":["Interviews"],"summary":"Extract Candidate From Resume","description":"Extract candidate information from resume file (PDF/DOCX) using AI\n\nThis endpoint allows extraction BEFORE creating the interview.\nUpload a resume and get candidate info back immediately.\nRequires: admin or recruiter role","operationId":"extract_candidate_from_resume_api_interviews_extract_from_resume_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_extract_candidate_from_resume_api_interviews_extract_from_resume_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractCandidateInfoResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/interviews/":{"post":{"tags":["Interviews"],"summary":"Create Interview","description":"Create a new interview\n\nRequires: admin or recruiter role\nCreates interview record linked to current user's organization","operationId":"create_interview_api_interviews__post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Interviews"],"summary":"List Interviews","description":"List all interviews for current user's organization\n\nSupports pagination, status filtering, and candidate-identity filtering\n(candidate_id and/or candidate_email — see build_interview_list_where).","operationId":"list_interviews_api_interviews__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}},{"name":"candidate_email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Email"}},{"name":"candidate_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Candidate Id"}},{"name":"mine","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Mine"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__interviews__models__InterviewListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/available-skills":{"get":{"tags":["Interviews"],"summary":"Available Skills","description":"Runnable specialist skill rounds (id + display name) for the schedule form.\n\nOnly skills with a RUNNABLE module — the library may list a skill (for the\nrecommender) before its assessment module exists; those aren't schedulable.\nDefined BEFORE `/{interview_id}` so FastAPI doesn't treat \"available-skills\"\nas an interview id.","operationId":"available_skills_api_interviews_available_skills_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/interviews/{interview_id}":{"get":{"tags":["Interviews"],"summary":"Get Interview","description":"Get interview details\n\nValidates that interview belongs to current user's organization","operationId":"get_interview_api_interviews__interview_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Interviews"],"summary":"Update Interview","description":"Update interview\n\nValidates that interview belongs to current user's organization\nRequires: admin or recruiter role","operationId":"update_interview_api_interviews__interview_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Interviews"],"summary":"Delete Interview","description":"Delete interview\n\nValidates that interview belongs to current user's organization\nRequires: admin role","operationId":"delete_interview_api_interviews__interview_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/reschedule":{"post":{"tags":["Interviews"],"summary":"Reschedule Interview Endpoint","description":"Move an interview in place (ADR 0005, Phase 2). Thin wrapper — all\nbusiness logic lives in core.interview_reschedule (CLAUDE.md §3).","operationId":"reschedule_interview_endpoint_api_interviews__interview_id__reschedule_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewRescheduleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewRescheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/resend-invite":{"post":{"tags":["Interviews"],"summary":"Resend Invite","description":"Re-send the candidate invitation email for an existing interview.\n\nReplaces the old FE-direct POST to /api/interviews/send-invitation, which\nsent neither company_name nor job_title — the email route's trust guard\n400s when both are blank, so the resend button silently failed. Identity\nMUST resolve server-side (resolve_for_read applies the role's CURRENT\nvisibility — a role flipped to Confidential after dispatch must hide the\nname on resends too), and resolving here also restores the brain framing,\ncoding heads-up, and plan-round callout the FE path couldn't compute.","operationId":"resend_invite_api_interviews__interview_id__resend_invite_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/void":{"post":{"tags":["Interviews"],"summary":"Void Interview Endpoint","description":"Mark an interview as invalid so it stops counting in role\nbenchmarks, candidate counts, and pipeline aggregations. The\ninterview record (transcript, recording, analysis if any) is\npreserved — voiding is a data-hygiene action, not a delete.\n\nRefunds are explicitly out of scope. Voiding does not touch the\nwallet ledger; charge disputes are a separate manual flow. This\ndecoupling is intentional anti-abuse design.\n\nRequires admin or recruiter role.","operationId":"void_interview_endpoint_api_interviews__interview_id__void_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidInterviewRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidInterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/unvoid":{"post":{"tags":["Interviews"],"summary":"Unvoid Interview Endpoint","description":"Reverse a void. Restores the interview to active state and clears\nthe audit metadata. Use case: recruiter accidentally voided the\nwrong row, or context changed and the interview is signal again.\n\nRequires admin or recruiter role.","operationId":"unvoid_interview_endpoint_api_interviews__interview_id__unvoid_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidInterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/confirm-roles":{"post":{"tags":["Interviews"],"summary":"Confirm Speaker Roles Endpoint","description":"Answer the \"who was the candidate?\" question for an interview whose\ntranscript participants couldn't be confidently matched to the\nscheduled candidate. Re-tags the transcript with the confirmed\nroles and queues the analysis that was paused — or, for a no-show,\nvoids the interview so no score is fabricated.\n\nRequires admin or recruiter role.","operationId":"confirm_speaker_roles_endpoint_api_interviews__interview_id__confirm_roles_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmSpeakerRolesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmSpeakerRolesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/transcribe":{"post":{"tags":["Interviews"],"summary":"Transcribe Interview","description":"Trigger transcription for interview audio\n\nCreates a transcription job that will be processed by background worker\nRequires: interviewer, recruiter, or admin role","operationId":"transcribe_interview_api_interviews__interview_id__transcribe_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/transcript":{"get":{"tags":["Interviews"],"summary":"Get Transcript","description":"Get transcript for interview\n\nReturns the most recent completed transcription","operationId":"get_transcript_api_interviews__interview_id__transcript_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__interviews__models__TranscriptResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/analyze":{"post":{"tags":["Interviews"],"summary":"Analyze Interview","description":"Trigger AI analysis for interview\n\nCreates an analysis job that will be processed by background worker\nRequires completed transcription\n\nArgs:\n    interview_id: UUID of the interview\n    request: Optional analysis request with report_type ('recruiter' or 'candidate')\n    current_user: Authenticated user from dependency\n\nReturns:\n    Analysis job creation status","operationId":"analyze_interview_api_interviews__interview_id__analyze_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AnalysisRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/processing-status":{"get":{"tags":["Interviews"],"summary":"Get Processing Status","description":"Get current processing status for interview\n\nReturns transcription and analysis job statuses","operationId":"get_processing_status_api_interviews__interview_id__processing_status_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessingStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/upload-audio":{"post":{"tags":["Interviews"],"summary":"Upload Audio For Test","description":"Upload audio file for test mode interview\n\nAllows manual audio upload for testing the analysis pipeline\nOnly works for interviews created with is_test_mode=true","operationId":"upload_audio_for_test_api_interviews__interview_id__upload_audio_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_audio_for_test_api_interviews__interview_id__upload_audio_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/upload-transcript":{"post":{"tags":["Interviews"],"summary":"Upload Transcript For Test","description":"Upload transcript file for test mode interview.\n\nAllows direct transcript upload for testing the analysis pipeline\nwithout requiring audio transcription.\n\nSupports:\n- JSON format: {\"dialogue\": [{\"speaker\": \"interviewer\", \"text\": \"...\"}]}\n- Plain text format: INTERVIEWER: text... CANDIDATE: text...\n\nOnly works for interviews created with is_test_mode=true","operationId":"upload_transcript_for_test_api_interviews__interview_id__upload_transcript_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_transcript_for_test_api_interviews__interview_id__upload_transcript_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/reset":{"post":{"tags":["Interviews"],"summary":"Reset Interview Processing","description":"Reset interview processing to restart from transcript.\nClears analysis_status and analysis_results to trigger reprocessing.","operationId":"reset_interview_processing_api_interviews__interview_id__reset_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/communication-metrics":{"get":{"tags":["Interviews"],"summary":"Get Communication Metrics","description":"Get communication metrics for interview\n\nReturns:\n    Communication metrics including filler words, pauses, speaking pace, and fluency scores\n\nPhase 1 Feature: Communication Metrics & Progress Tracking","operationId":"get_communication_metrics_api_interviews__interview_id__communication_metrics_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/audio":{"get":{"tags":["Interviews"],"summary":"Stream Audio","description":"Stream audio file for interview.\n\nHandles both local file:// URLs (dev mode) and remote S3/GCS URLs (production).\nFor file:// URLs, reads and streams the file directly.\nFor remote URLs, redirects to the signed URL.\n\nReturns:\n    FileResponse for local files, RedirectResponse for remote URLs","operationId":"stream_audio_api_interviews__interview_id__audio_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/context":{"get":{"tags":["Interviews"],"summary":"Get Interview Context","description":"Public endpoint to get interview context for Vapi Web SDK.\n\nReturns the information needed to configure the Vapi assistant\n(system prompt, first message, candidate details) so the frontend\ncan start the call using the Vapi Web SDK.\n\nNo authentication required - candidates don't have accounts.","operationId":"get_interview_context_api_interviews__interview_id__context_get","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/set-call-id":{"post":{"tags":["Interviews"],"summary":"Set Call Id","description":"Save Vapi call_id to interview record.\n\nCalled by the frontend when Vapi Web SDK call starts.\nThis enables the webhook handler to match incoming webhooks to interviews.\n\nNo authentication required - called from candidate's browser.","operationId":"set_call_id_api_interviews__interview_id__set_call_id_post","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCallIdRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/call-ended":{"post":{"tags":["Interviews"],"summary":"Handle Call Ended Fallback","description":"Handle call ended event from frontend (fallback for webhooks).\n\nUsed when Vapi webhooks can't reach the backend (e.g., localhost development).\nSaves transcript in standard The Better Hire format and triggers screening analysis.\n\nNo authentication required - called from candidate's browser.","operationId":"handle_call_ended_fallback_api_interviews__interview_id__call_ended_post","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallEndedRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/join-video":{"post":{"tags":["Interviews"],"summary":"Join Video Screening","description":"Create a Vapi web call for video screening - called when candidate clicks 'Join'.\n\nThis endpoint is called when the candidate is ready to join the video screening.\nIt creates a Vapi web call on-demand (rooms expire quickly, ~30 seconds).\n\nThe candidate does NOT need to be authenticated - they join via a link.\n\nReturns:\n    webCallUrl: URL to redirect candidate to (vapi.daily.co room)","operationId":"join_video_screening_api_interviews__interview_id__join_video_post","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/join":{"get":{"tags":["Interviews"],"summary":"Join Video Screening Redirect","description":"Public endpoint for candidates to join video screening.\n\nCalled when candidate clicks the email link. Creates Vapi room on-demand\nand redirects to the Vapi video room.\n\nNo authentication required - candidates don't have accounts.","operationId":"join_video_screening_redirect_api_interviews__interview_id__join_get","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/report.pdf":{"get":{"tags":["Interviews"],"summary":"Download interview report as PDF (dashboard, cookie auth)","operationId":"download_interview_report_pdf_api_interviews__interview_id__report_pdf_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/brain/start":{"post":{"tags":["Interviews"],"summary":"Brain Start","description":"Initialize a brain session for this interview. Returns the first question.\n\nThis is the admin/QA harness path — used by the internal Brain\nSessions tool, not by candidates. Customer-facing brain interviews\nstart via Vapi (interview_join.py → Vapi `call.started` →\nvoice_webhooks.handle_call_started → deduct_action). Per ADR-0002,\nthis admin path is excluded from billing.","operationId":"brain_start_api_interviews__interview_id__brain_start_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BrainStartRequest"}],"default":{},"title":"Request"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrainStartResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/brain/turn":{"post":{"tags":["Interviews"],"summary":"Brain Turn","description":"Submit one candidate utterance, get next question.","operationId":"brain_turn_api_interviews__interview_id__brain_turn_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrainTurnRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrainTurnResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/brain/finalize":{"post":{"tags":["Interviews"],"summary":"Brain Finalize","description":"Terminate the brain session, persist transcript_segments + brain_analysis,\nand trigger the existing analyzer pipeline.","operationId":"brain_finalize_api_interviews__interview_id__brain_finalize_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrainFinalizeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/brain/state":{"get":{"tags":["Interviews"],"summary":"Brain State","description":"Read-only debug peek used by the admin harness sidebar.","operationId":"brain_state_api_interviews__interview_id__brain_state_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrainStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/":{"post":{"tags":["Invitations"],"summary":"Send Invitation","description":"Send invitation to new team member\n\nRequires: admin role\nCreates invitation with 7-day expiry\nSends invitation email in background","operationId":"send_invitation_api_invitations__post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Invitations"],"summary":"List Invitations","description":"List all invitations for current user's organization\n\nSupports pagination and status filtering\nRequires: admin role","operationId":"list_invitations_api_invitations__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/validate/{invitation_token}":{"get":{"tags":["Invitations"],"summary":"Validate Invitation","description":"Validate invitation token (public endpoint)\n\nReturns invitation details if valid\nUsed by invitation acceptance page","operationId":"validate_invitation_api_invitations_validate__invitation_token__get","parameters":[{"name":"invitation_token","in":"path","required":true,"schema":{"type":"string","title":"Invitation Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/accept":{"post":{"tags":["Invitations"],"summary":"Accept Invitation","description":"Accept invitation and create user account (public endpoint)\n\nCreates user account and marks invitation as accepted\nReturns auth tokens for immediate login","operationId":"accept_invitation_api_invitations_accept_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationAcceptRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/{invitation_id}/resend":{"post":{"tags":["Invitations"],"summary":"Resend Invitation","description":"Resend a pending invitation: bump expiry to +7d and re-send the email.\n\nKeeps the same token so already-shared links still work; also un-expires\na lapsed pending invite (the common reason to resend). Admin-only,\npending-only. (2026-08-01 — invite lifecycle actions.)","operationId":"resend_invitation_api_invitations__invitation_id__resend_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Invitation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/{invitation_id}":{"delete":{"tags":["Invitations"],"summary":"Revoke Invitation","description":"Revoke/cancel pending invitation\n\nRequires: admin role\nOnly pending invitations can be revoked","operationId":"revoke_invitation_api_invitations__invitation_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Invitation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/partnerships":{"post":{"tags":["Invitations"],"summary":"Send Partnership Invitation","description":"Send partnership invitation to another organization\n\nRequires: admin role\nCreates invitation with 7-day expiry\nSends invitation email in background\nBidirectional: works for both corporate inviting agencies and vice versa","operationId":"send_partnership_invitation_api_invitations_partnerships_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnershipInvitationCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnershipInvitationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Invitations"],"summary":"List Partnership Invitations","description":"List all partnership invitations for current user's organization\n\nRequires: admin role","operationId":"list_partnership_invitations_api_invitations_partnerships_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PartnershipInvitationResponse"},"title":"Response List Partnership Invitations Api Invitations Partnerships Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/partnerships/verify/{invitation_token}":{"get":{"tags":["Invitations"],"summary":"Verify Partnership Invitation","description":"Verify partnership invitation token (public endpoint)\n\nReturns invitation details if valid\nUsed by signup page to pre-fill organization details","operationId":"verify_partnership_invitation_api_invitations_partnerships_verify__invitation_token__get","parameters":[{"name":"invitation_token","in":"path","required":true,"schema":{"type":"string","title":"Invitation Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/partnerships/accept":{"post":{"tags":["Invitations"],"summary":"Accept Partnership Invitation","description":"Accept a partnership invitation: create the partner org + its admin\nuser + the active partnership, atomically.\n\nPorted from the direct-DB Next.js route (API proxy sweep slice 6) —\npreviously the ONLY auth-group route with no BE counterpart at all.\nFaithful quirks kept: `invited_at` binds the invitation's created_at\n(the row predates acceptance), plan is 'free_trial' (partnership\nsignups, vs 'trial' for self-serve register), partnership starts\n'active' with approved_at=NOW() (accepting IS the approval). One\ndeliberate normalization: the session token is the standard\ncreate_access_token (15-min, refreshable) — the old route minted a\nnonstandard 24h JWT with an extra user_id claim.","operationId":"accept_partnership_invitation_api_invitations_partnerships_accept_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnershipAcceptRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/partnerships/{invitation_id}":{"delete":{"tags":["Invitations"],"summary":"Revoke Partnership Invitation","description":"Revoke/cancel pending partnership invitation\n\nRequires: admin role\nOnly pending invitations can be revoked","operationId":"revoke_partnership_invitation_api_invitations_partnerships__invitation_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Invitation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/team/":{"get":{"tags":["Team"],"summary":"List Team Members","description":"List all team members in current user's organization\n\nSupports pagination and filtering by role/status\nAll authenticated users can view team members","operationId":"list_team_members_api_team__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"role_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role Filter"}},{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamMemberListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/team/overview":{"get":{"tags":["Team"],"summary":"Team Overview","description":"Combined team view for the settings/team page: members (role-priority\nordered) + pending invitations, in the exact shape the old direct-DB\n/api/team/members route returned. Declared BEFORE /{user_id} so the static\n'overview' segment wins route matching.","operationId":"team_overview_api_team_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/team/{user_id}":{"get":{"tags":["Team"],"summary":"Get Team Member","description":"Get team member details\n\nValidates that user belongs to current user's organization","operationId":"get_team_member_api_team__user_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamMemberResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Team"],"summary":"Update Team Member","description":"Update team member\n\nRequires: admin role\nCannot update own role or status\nValidates that user belongs to current user's organization","operationId":"update_team_member_api_team__user_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamMemberUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamMemberResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Team"],"summary":"Remove Team Member","description":"Remove team member (soft delete)\n\nRequires: admin role\nCannot remove self\nValidates that user belongs to current user's organization","operationId":"remove_team_member_api_team__user_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Team"],"summary":"Update Team Member Role","description":"Role-only update — the proxy target for the Next.js PATCH\n/api/team/members/[id]. Admin-only, last-admin-demote guard, audit log,\nand the exact {success, message, member:{id, role}} response shape the FE\ncontract expects.","operationId":"update_team_member_role_api_team__user_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamMemberRoleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/recommend-skills":{"post":{"tags":["Job Roles"],"summary":"Recommend Skills From Jd","description":"Layer-2 recommender over an inline JD blob. Same proposal semantics as\nthe per-role route below — including that suggestions are NOT filtered to\nrunnable skills; see that route's docstring for where the filtering lives.\nUsed by the role editor's \"Suggest rounds\"\n(docs/design/role-assessment-plan.md Phase 1).","operationId":"recommend_skills_from_jd_api_job_roles_recommend_skills_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecommendSkillsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/{role_id}/recommend-skills":{"post":{"tags":["Job Roles"],"summary":"Recommend Role Skills","description":"Layer-2 recommender: suggest which specialist skill rounds this role's JD\ndemands (weight 1-3 + a JD-grounded rationale). A PROPOSAL the recruiter\napproves/tunes — not a decision. Behavioral is always-on and never listed.\n\nRUNNABLE-ness is NOT filtered here. The recommender matches against the full\nskill LIBRARY (specialists/library.py), which deliberately carries signatures\nfor skills whose assessment module doesn't exist yet — so this route can\nreturn e.g. analytical_data while nothing can actually run it.\n\nThree guards downstream keep that from reaching a candidate, and they are\nwhere the filtering genuinely lives:\n  - GET /api/interviews/available-skills returns only `runnable` entries, so\n    the schedule form and the assessment-plan editor never offer the rest\n  - the plan editor filters suggestions against that same list again\n  - the brain's orchestrator raises \"Unknown skill: <id>\" if a session is\n    somehow started for a skill with no module\n\nDo not \"fix\" this by filtering here: an unrunnable suggestion is the signal\nthat a customer's JD wants a skill we haven't built, which is exactly the\ninput for deciding what to build next.","operationId":"recommend_role_skills_api_job_roles__role_id__recommend_skills_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Role Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/":{"post":{"tags":["Job Roles"],"summary":"Create Job Role","description":"Create a new job role\n\nRequires: admin or recruiter role","operationId":"create_job_role_api_job_roles__post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__job_roles__JobRoleCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__job_roles__JobRoleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Job Roles"],"summary":"List Job Roles","description":"List all job roles for current user's organization\n\nSupports pagination and status filtering","operationId":"list_job_roles_api_job_roles__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}},{"name":"mine","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Mine"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__job_roles__JobRoleListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/similar":{"get":{"tags":["Job Roles"],"summary":"Find Similar Job Roles","description":"Soft duplicate-detection at create time. Returns up to 3 active\nroles in the same org whose titles overlap with `title`. Used by\nthe create-role form to surface a non-blocking \"similar role\nexists\" banner so recruiters don't fragment the candidate\npipeline by accidentally creating \"Sr PD\" and \"Senior Product\nDesigner\" as separate roles.\n\nMatching is intentionally simple (case-insensitive substring in\neither direction, plus exact match). Doesn't catch abbreviation\ndifferences like \"Sr\" vs \"Senior\" — fuzzy matching via pg_trgm\nis a future iteration once we see real-world false negatives.","operationId":"find_similar_job_roles_api_job_roles_similar_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"title","in":"query","required":true,"schema":{"type":"string","title":"Title"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/match-preview":{"post":{"tags":["Job Roles"],"summary":"Match Preview","description":"Score a single résumé against 1-5 active job roles in parallel.\nStateless preview — no candidate is created. The recruiter sees a\nranked table and then commits one (or more) of the results via the\nexisting add-candidate-to-role endpoint.\n\nBuilt for the agency flow where one résumé might fit Tesco Sr SE,\nWalmart Sr SE, and Stripe Sr SE simultaneously and the recruiter\nwants the comparison before picking where to send the screening\ninvite. In-house TAs benefit too — match one résumé against several\nof their own open positions without re-uploading.\n\nCap of 5 roles per request bounds latency (~5s) and LLM spend\nper click. Match results land in the existing match_results cache\n(compute_match handles dedup by sha256(jd + résumé)), so resubmitting\nthe same résumé + selection costs nothing.","operationId":"match_preview_api_job_roles_match_preview_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_match_preview_api_job_roles_match_preview_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchPreviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/hiring-companies":{"get":{"tags":["Job Roles"],"summary":"List Hiring Companies","description":"Distinct hiring companies the tenant currently has on active roles,\nwith a role count per company. Used by the New Role + Edit Identity\nflows to autocomplete the Company field — so an agency recruiter\ntyping \"T\" sees their existing Tesco client instead of accidentally\ncreating \"Tesco PLC\" as a parallel record.\n\nActive-only by design: a closed client's name shouldn't pollute\nsuggestions for a fresh role. Empty / whitespace-only displays are\nfiltered out (Generic roles with no internal label have no name\nto suggest).","operationId":"list_hiring_companies_api_job_roles_hiring_companies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HiringCompaniesResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/{job_role_id}":{"get":{"tags":["Job Roles"],"summary":"Get Job Role","description":"Get job role details\n\nValidates that job role belongs to current user's organization","operationId":"get_job_role_api_job_roles__job_role_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__job_roles__JobRoleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Job Roles"],"summary":"Update Job Role","description":"Update job role\n\nValidates that job role belongs to current user's organization\nRequires: admin or recruiter role","operationId":"update_job_role_api_job_roles__job_role_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRoleUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__job_roles__JobRoleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Job Roles"],"summary":"Delete Job Role","description":"Delete (soft delete) job role\n\nValidates that job role belongs to current user's organization\nRequires: admin role","operationId":"delete_job_role_api_job_roles__job_role_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/{job_role_id}/benchmark":{"get":{"tags":["Job Roles"],"summary":"Get Job Role Benchmark","description":"Per-role hiring benchmark — averages, top performer, and (when N is\nhigh enough) a top-quartile threshold based on the org's own\ncompleted interviews. Cold-start tier returned in `tier` so the UI\ncan render the right state without re-implementing the gating.\n\nScoped to the caller's organization. Verifies role ownership before\naggregating — never leaks data from another org through a guessed UUID.","operationId":"get_job_role_benchmark_api_job_roles__job_role_id__benchmark_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRoleBenchmarkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/{job_role_id}/hiring-company":{"get":{"tags":["Job Roles"],"summary":"Get Role Hiring Company","description":"Resolve the candidate-facing hiring company identity for a role.\n\nUsed by the FE to render the preview line on the JD form, the\nneeds-review badge on role list/detail, and the at-dispatch toast\nwhen scheduling against an unset role. Tenant-scoped — never returns\nanother org's data even with a guessed UUID.","operationId":"get_role_hiring_company_api_job_roles__job_role_id__hiring_company_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HiringCompanyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/extract-competencies":{"post":{"tags":["Job Roles"],"summary":"Extract Competencies","description":"Extract competencies from JD text or file using AI\n\nProvide either jd_text OR job_role_id (to extract from uploaded file)\nRequires: admin or recruiter role","operationId":"extract_competencies_api_job_roles_extract_competencies_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractCompetenciesRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractCompetenciesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/extract-jd-text":{"post":{"tags":["Job Roles"],"summary":"Extract Jd Text Only","description":"Lightweight: extract JUST the plain text from a JD file (PDF/DOCX/TXT)\nwithout running competency extraction. Used by the create-role\nUpload tab so the JD Quality scorecard can fire as soon as the\nfile is uploaded — competency extraction is a separate, heavier\nstep (own button or auto on submit).","operationId":"extract_jd_text_only_api_job_roles_extract_jd_text_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_extract_jd_text_only_api_job_roles_extract_jd_text_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/expand-competency":{"post":{"tags":["Job Roles"],"summary":"Expand Competency","description":"Validate + expand a single competency name into a description and\nevaluation criteria. Thin router: validates inputs, delegates the\nLLM work to `core.jd_parser.expand_single_competency`, maps the\nresult onto the API response. All AI logic stays in core/ so the\npublic API and MCP can call the same service later.","operationId":"expand_competency_api_job_roles_expand_competency_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpandCompetencyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpandCompetencyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/extract-competencies-from-file":{"post":{"tags":["Job Roles"],"summary":"Extract Competencies From Uploaded File","description":"Extract competencies from uploaded JD file (PDF/DOCX) using AI\n\nThis endpoint allows extraction BEFORE saving the job role.\nUpload a file and get competencies back immediately.\n\nParameters:\n- extract_jd_text: If True, returns full JD text along with competencies\n\nRequires: admin or recruiter role","operationId":"extract_competencies_from_uploaded_file_api_job_roles_extract_competencies_from_file_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_extract_competencies_from_uploaded_file_api_job_roles_extract_competencies_from_file_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractCompetenciesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/{job_role_id}/upload-jd":{"post":{"tags":["Job Roles"],"summary":"Upload Jd File","description":"Upload JD file (PDF/DOCX) for a job role\n\nValidates that job role belongs to current user's organization\nRequires: admin or recruiter role","operationId":"upload_jd_file_api_job_roles__job_role_id__upload_jd_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_jd_file_api_job_roles__job_role_id__upload_jd_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/generate-jd":{"post":{"tags":["Job Roles"],"summary":"Generate Job Description","description":"Generate a job description using AI based on job title, department, and key requirements\n\nThis endpoint helps recruiters quickly create comprehensive JDs by providing:\n- Job title (required)\n- Department (optional, for context)\n- Key requirements (3-5 bullet points of must-have skills/qualifications)\n\nThe AI will generate a complete JD with:\n- Role overview\n- Responsibilities\n- Qualifications\n- Culture fit\n\nRequires: admin or recruiter role","operationId":"generate_job_description_api_job_roles_generate_jd_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__job_roles__GenerateJDRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateJDResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/{job_role_id}/candidates":{"get":{"tags":["Job Roles"],"summary":"Get Job Role Candidates","description":"Get all candidates for a specific job role\n\nReturns candidates with their source, stage, and latest interview info.\nIncludes:\n1. Candidates with interviews for this job role\n2. Candidates directly added via job_role_candidates (no interview yet)","operationId":"get_job_role_candidates_api_job_roles__job_role_id__candidates_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}},{"name":"source_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Filter"}},{"name":"stage_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Filter"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRoleCandidatesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Job Roles"],"summary":"Add Candidate To Job Role","description":"Add a candidate to a job role (without scheduling an interview)\n\nSupports two modes:\n1. Link existing candidate: Provide candidate_id\n2. Create new candidate: Provide name, email, and optionally phone, source, notes\n\nOptionally include a resume_file which will be stored on the candidate record.","operationId":"add_candidate_to_job_role_api_job_roles__job_role_id__candidates_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"allOf":[{"$ref":"#/components/schemas/Body_add_candidate_to_job_role_api_job_roles__job_role_id__candidates_post"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/{job_role_id}/candidates/{candidate_id}":{"delete":{"tags":["Job Roles"],"summary":"Remove Candidate From Job Role","description":"Remove a candidate from a job role\n\nThis removes the direct link from job_role_candidates table.\nIt does NOT delete the candidate or any existing interviews.","operationId":"remove_candidate_from_job_role_api_job_roles__job_role_id__candidates__candidate_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}},{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/job-roles/improve-jd":{"post":{"tags":["Job Roles"],"summary":"Improve Job Description","description":"Revise an existing JD to address a curated set of fixes from the\nquality scorecard. Used by Paste/Upload tabs — same UX as\nGenerate's \"Regenerate with fixes\" but starts from the user's\nexisting text instead of a blank slate, so we keep their content\nand just patch the flagged issues.\n\nThe LLM is told to PRESERVE the original structure and content\nwhere possible; revisions should be surgical, not stylistic.","operationId":"improve_job_description_api_job_roles_improve_jd_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImproveJDRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateJDResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/generate-jd-pdf":{"post":{"tags":["Job Roles"],"summary":"Generate Job Description Pdf","description":"Generate a PDF file from job description text\n\nTakes the JD text and creates a professionally formatted PDF document.\nReturns the PDF file as a downloadable response.\n\nRequires: admin or recruiter role","operationId":"generate_job_description_pdf_api_job_roles_generate_jd_pdf_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateJDPDFRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/job-roles/{job_role_id}/interviews":{"get":{"tags":["Job Roles"],"summary":"Get Job Role Interviews","description":"Get interviews for a specific job role\n\nReturns interviews with candidate info, status, score, and recommendation.\nDefault limit is 5 for the preview table.","operationId":"get_job_role_interviews_api_job_roles__job_role_id__interviews_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Role Id"}},{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobRoleInterviewsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview-stages/templates":{"get":{"tags":["Interview Stages"],"summary":"List Stage Templates","description":"List interview pipeline templates: global templates (organization_id\nIS NULL) merged with this org's custom templates. Org-custom first\n(ORDER BY organization_id DESC NULLS LAST), then by name.","operationId":"list_stage_templates_api_interview_stages_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewStageTemplateListResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/interview-stages/{stage_id}":{"get":{"tags":["Interview Stages"],"summary":"Get Interview Stage","description":"Get a single interview stage. Scoped to the caller's organization.","operationId":"get_interview_stage_api_interview_stages__stage_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"stage_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Stage Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewStageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Interview Stages"],"summary":"Update Interview Stage","description":"Partial update. Builds a dynamic UPDATE from only the fields provided\n(mirror of the old route's `if (body[field] !== undefined)` whitelist).\nEmpty payload -> 400, matching the prior behavior.","operationId":"update_interview_stage_api_interview_stages__stage_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"stage_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Stage Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewStageUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewStageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Interview Stages"],"summary":"Delete Interview Stage","description":"Soft delete a stage (sets deleted_at + updated_at).","operationId":"delete_interview_stage_api_interview_stages__stage_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"stage_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Stage Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/support":{"post":{"tags":["Support"],"summary":"Create Support Request","operationId":"create_support_request_api_support_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/support/contact":{"post":{"tags":["Support"],"summary":"Create Public Contact","description":"Anonymous message from the marketing site — no auth, by design.\n\nUnauthenticated write on a domain we actively advertise, so it carries the\nstrict per-IP auth-tier limit (core/rate_limit.get_client_identifier falls\nback to IP when there is no user) plus the honeypot below.\n\n`request` is required by slowapi's decorator, which resolves the client\nidentifier from it — removing the parameter breaks the limit silently.","operationId":"create_public_contact_api_support_contact_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicContactRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/hiring-decisions":{"post":{"tags":["Hiring Decisions"],"summary":"Create Hiring Decision","description":"Upsert a hiring decision for an application. job_position_id is derived\nfrom the application (never client-supplied); decided_by comes from the\ntoken. ON CONFLICT(application_id) updates the existing decision. The\nmig-017 triggers handle applications.status + job_positions.filled_count.","operationId":"create_hiring_decision_api_hiring_decisions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HiringDecisionCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HiringDecisionRow"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/hiring-decisions/{decision_id}":{"get":{"tags":["Hiring Decisions"],"summary":"Get Hiring Decision","description":"Get a hiring decision enriched with decider name + candidate + position\ncode (the 4 LEFT JOIN columns the FE reads).","operationId":"get_hiring_decision_api_hiring_decisions__decision_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"decision_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Decision Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HiringDecisionEnriched"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Hiring Decisions"],"summary":"Update Hiring Decision","description":"Partial update. PUT-ing `decision` re-fires the mig-017 triggers (e.g.\noffer_accepted increments filled_count) — handled in Postgres, not here.","operationId":"update_hiring_decision_api_hiring_decisions__decision_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"decision_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Decision Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HiringDecisionUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HiringDecisionRow"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/evaluations":{"post":{"tags":["Evaluations"],"summary":"Create Evaluation","description":"Create a human evaluation. evaluator_type is forced to 'human' and\nevaluator_user_id comes from the token (the old route used the NextAuth\nsession).","operationId":"create_evaluation_api_evaluations_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationRow"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/evaluations/{eval_id}":{"get":{"tags":["Evaluations"],"summary":"Get Evaluation","description":"Get a single evaluation, scoped to the caller's organization.","operationId":"get_evaluation_api_evaluations__eval_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"eval_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Eval Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationRow"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Evaluations"],"summary":"Update Evaluation","description":"Partial update. competency_scores/evidence are json.dumps'd (JSONB);\nstrengths/weaknesses pass through as lists (TEXT[]).","operationId":"update_evaluation_api_evaluations__eval_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"eval_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Eval Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationRow"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Evaluations"],"summary":"Delete Evaluation","description":"Soft delete (sets deleted_at only — no updated_at, mirroring the old\nroute). Returns a bare {success: true}.","operationId":"delete_evaluation_api_evaluations__eval_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"eval_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Eval Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organizations/{org_id}/contacts":{"get":{"tags":["Organizations"],"summary":"List Contacts","description":"List an organization's contacts. Any authenticated member of the org\n(matches the old route — no role gate on read).","operationId":"list_contacts_api_organizations__org_id__contacts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Organizations"],"summary":"Create Contact","description":"Add a contact. No role gate (matches the old route).","operationId":"create_contact_api_organizations__org_id__contacts_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organizations/{org_id}/contacts/{contact_id}":{"put":{"tags":["Organizations"],"summary":"Update Contact","description":"Update a contact. Admin-only (matches the old route).","operationId":"update_contact_api_organizations__org_id__contacts__contact_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Contact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Organizations"],"summary":"Delete Contact","description":"Soft delete a contact. Admin-only (matches the old route).","operationId":"delete_contact_api_organizations__org_id__contacts__contact_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Contact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organizations/{org_id}/agencies":{"get":{"tags":["Organizations"],"summary":"List Agencies","description":"List partner orgs (bidirectional). Corporate orgs see partner agencies\nwith performance metrics; agency orgs see partner corporates. `status` query\nparam filters (default 'active'; 'all' = no filter).","operationId":"list_agencies_api_organizations__org_id__agencies_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}},{"name":"status_filter","in":"query","required":false,"schema":{"type":"string","default":"active","title":"Status Filter"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Organizations"],"summary":"Invite Agency","description":"Invite an agency to partner (corporate-only).","operationId":"invite_agency_api_organizations__org_id__agencies_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgencyInvite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organizations/{org_id}/agencies/{partnership_id}":{"patch":{"tags":["Organizations"],"summary":"Update Partnership","description":"Drive the partnership state machine: approve/decline/suspend/activate/\nterminate, each only legal from specific source states.","operationId":"update_partnership_api_organizations__org_id__agencies__partnership_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}},{"name":"partnership_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Partnership Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnershipAction"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Organizations"],"summary":"Terminate Partnership","description":"Terminate a partnership AND cascade its active position assignments to\n'removed' — atomically, so a crash can't leave dangling active rows.","operationId":"terminate_partnership_api_organizations__org_id__agencies__partnership_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}},{"name":"partnership_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Partnership Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/PartnershipDelete"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organizations/{org_id}/settings":{"get":{"tags":["Organizations"],"summary":"Get Settings","description":"Fetch org settings (teams/locations/job_levels/employment_types).","operationId":"get_settings_api_organizations__org_id__settings_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Organizations"],"summary":"Update Settings","description":"Merge-update org settings (admin-only). JSONB shallow merge, with the\nsame key allowlist + job-level label/years validation the old route ran.","operationId":"update_settings_api_organizations__org_id__settings_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingsUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organizations/{org_id}/theme":{"get":{"tags":["Organizations"],"summary":"Get Theme","description":"Fetch the org theme overrides (bare object; {} when unset). The\nCache-Control header is set by the Next.js proxy layer.","operationId":"get_theme_api_organizations__org_id__theme_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Organizations"],"summary":"Update Theme","description":"Replace the org theme config (admin/owner only). Full overwrite.","operationId":"update_theme_api_organizations__org_id__theme_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"Theme Config"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Organizations"],"summary":"Reset Theme","description":"Reset the org theme to default (theme_config = NULL). Admin/owner only.","operationId":"reset_theme_api_organizations__org_id__theme_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organizations/{org_id}/logo":{"get":{"tags":["Organizations"],"summary":"Get Logo","description":"The org's logo as a URL the browser can load (signed), or null.","operationId":"get_logo_api_organizations__org_id__logo_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Organizations"],"summary":"Upload Logo","description":"Replace the org's logo (admin/owner only). PNG/JPEG/WebP/SVG, size-capped;\nsee core.domains.org.config. Returns the signed URL of the new logo.","operationId":"upload_logo_api_organizations__org_id__logo_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_logo_api_organizations__org_id__logo_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Organizations"],"summary":"Delete Logo","description":"Remove the org's logo (admin/owner only); the lobby shows the product mark again.","operationId":"delete_logo_api_organizations__org_id__logo_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"org_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Org Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dashboard/onboarding-status":{"get":{"tags":["Dashboard"],"summary":"Onboarding Status","description":"Lightweight onboarding progress for the dashboard checklist.","operationId":"onboarding_status_api_dashboard_onboarding_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/dashboard/interview-stats":{"get":{"tags":["Dashboard"],"summary":"Interview Stats","description":"Interview-focused dashboard aggregation (KPIs, review list, schedule,\npipeline distribution). Per-row score/recommendation/confidence come from\nthe canonical accessors — see core/domains/dashboard/service.py.","operationId":"interview_stats_api_dashboard_interview_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/dashboard/config":{"get":{"tags":["Dashboard"],"summary":"Get Dashboard Config","description":"The org's dashboard config, merged out to the full renderable shape.\n\nReplaces the direct-DB Next.js route that trusted the X-Organization-ID\nheader (API proxy sweep); org comes from the verified token.","operationId":"get_dashboard_config_api_dashboard_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"delete":{"tags":["Dashboard"],"summary":"Delete Dashboard Config","description":"Reset the org's dashboard config to defaults (admin only).","operationId":"delete_dashboard_config_api_dashboard_config_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"patch":{"tags":["Dashboard"],"summary":"Patch Dashboard Config","description":"Deep-merge a partial config into the stored one (admin only).\n\nThe role gate moves from the spoofable X-User-Role header to the\nverified token — this also un-breaks theme persistence for sessions\nthat never had the client-side user_role cookie (e.g. straight from\nregistration).","operationId":"patch_dashboard_config_api_dashboard_config_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardConfigPatch"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/form-config":{"get":{"tags":["settings"],"summary":"Get Form Config","description":"Get the current organization's form configuration\n\nReturns form field settings for job roles and interviews","operationId":"get_form_config_api_settings_form_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormConfigResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["settings"],"summary":"Update Form Config","description":"Update the organization's form configuration\n\nOnly admins can update form configuration","operationId":"update_form_config_api_settings_form_config_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormConfigUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormConfigResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/form-config/job-roles":{"get":{"tags":["settings"],"summary":"Get Job Roles Config","description":"Get just the job roles form configuration (lightweight endpoint)","operationId":"get_job_roles_config_api_settings_form_config_job_roles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/form-config/interviews":{"get":{"tags":["settings"],"summary":"Get Interviews Config","description":"Get just the interviews form configuration (lightweight endpoint)","operationId":"get_interviews_config_api_settings_form_config_interviews_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/test-mode":{"get":{"tags":["settings"],"summary":"Get Test Mode","description":"Get test mode status for the organization\n\nTest mode allows creating test interviews with manual audio upload\nfor testing the multi-agent analysis pipeline\n\nBetterHire staff only (2026-09-02). Non-staff get enabled=False rather\nthan a 404 because this is a state read, not the capability: the FE\ncalls it to decide whether to render the Test Mode card and the Test Lab\nnav entry, and reporting the EFFECTIVE state (off, because they may no\nlonger use it) makes both disappear with no client change.\n\nThat also means no data migration: an organisation that had the toggle\non keeps a stale `testMode.enabled` row which is now simply never\nhonoured. Reverting the policy is a code change, not a backfill.","operationId":"get_test_mode_api_settings_test_mode_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["settings"],"summary":"Update Test Mode","description":"Enable or disable test mode for the organization\n\nBetterHire staff only (2026-09-02). 404 rather than 403 follows the\nconvention in core/internal_access.py: for staff tooling a 403 confirms\nthe endpoint exists. The create path uses 403 instead, deliberately —\nthere the caller sent a flag and deserves to know why it was refused.\n\nNote this is the WEAKER of the two gates. The real one is in\ninterview_routes.create_interview: `is_test_mode` came straight from the\nclient with no validation, so before that gate this toggle controlled\nnothing but UI visibility.\n\nTest mode allows manual audio upload and testing of the analysis pipeline","operationId":"update_test_mode_api_settings_test_mode_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestModeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/mcp-status":{"get":{"tags":["settings"],"summary":"Get Mcp Status","description":"Check if the org has an active MCP/API key and when it was last used.","operationId":"get_mcp_status_api_settings_mcp_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/domains":{"get":{"tags":["settings"],"summary":"List Domains","description":"All domains for the org, newest first.","operationId":"list_domains_api_settings_domains_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["settings"],"summary":"Add Domain","description":"Register a domain and mint its DNS TXT verification token.","operationId":"add_domain_api_settings_domains_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainAddRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/domains/{domain_id}":{"get":{"tags":["settings"],"summary":"Get Domain","description":"One domain + its verification instructions while pending.","operationId":"get_domain_api_settings_domains__domain_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["settings"],"summary":"Delete Domain","description":"Remove a domain — blocked while an enabled SSO config references it.","operationId":"delete_domain_api_settings_domains__domain_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/settings/domains/{domain_id}/verify":{"post":{"tags":["settings"],"summary":"Verify Domain","description":"Check the DNS TXT record and flip the domain to verified.\n\nVerification failures are HTTP 200 with {verified: false, error} — the\nFE treats them as a state, not a request error.","operationId":"verify_domain_api_settings_domains__domain_id__verify_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/settings/sso":{"get":{"tags":["settings"],"summary":"List Sso Configs","operationId":"list_sso_configs_api_settings_sso_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["settings"],"summary":"Create Sso Config","operationId":"create_sso_config_api_settings_sso_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSOConfigCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/sso/{config_id}":{"get":{"tags":["settings"],"summary":"Get Sso Config","operationId":"get_sso_config_api_settings_sso__config_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"string","title":"Config Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["settings"],"summary":"Update Sso Config","operationId":"update_sso_config_api_settings_sso__config_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"string","title":"Config Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSOConfigUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["settings"],"summary":"Delete Sso Config","operationId":"delete_sso_config_api_settings_sso__config_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"string","title":"Config Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/settings/api-keys":{"get":{"tags":["settings"],"summary":"List Api Keys Endpoint","description":"List the org's public API keys. Never returns key material.","operationId":"list_api_keys_endpoint_api_settings_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["settings"],"summary":"Create Api Key Endpoint","description":"Mint a key. The plaintext is in this response and NOWHERE else — it is\nstored only as a bcrypt hash, so it cannot be shown again.","operationId":"create_api_key_endpoint_api_settings_api_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/settings/api-keys/{key_id}":{"delete":{"tags":["settings"],"summary":"Revoke Api Key Endpoint","description":"Revoke immediately. Soft-delete, so usage history survives — on a\nleaked key that history is the only record of what it did.","operationId":"revoke_api_key_endpoint_api_settings_api_keys__key_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jd-quality":{"post":{"tags":["JD Quality"],"summary":"Check Jd Quality","description":"Evaluate a JD against 7 industry-agnostic quality dimensions.\nReturns: {overall: 0-100, dimensions: {...}, note?: string}","operationId":"check_jd_quality_api_jd_quality_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JDQualityRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/jd-quality/auto-improve":{"post":{"tags":["JD Quality"],"summary":"Auto Improve And Evaluate","description":"Single-shot auto-improve + evaluate. The studio calls this when a\nJD first lands (Generate / Paste / Upload). Two LLM calls\nserver-side:\n  1. auto_improve_jd  → returns revised_jd + auto_applied_changes\n  2. evaluate_jd_quality(revised_jd) → returns score + remaining issues\n\nThe frontend then shows: improved JD in textarea, \"We auto-applied\nN improvements\" list, and a needs-input section for any remaining\nissues that require recruiter values (salary / company / location).\n\nReturns:\n  {\n    overall: 0-100,                     # score on the REVISED JD\n    dimensions: {...},                   # remaining issues only\n    revised_jd: \"<full text>\",\n    auto_applied_changes: [\"...\"],\n    original_text_length: int,           # for \"X chars → Y chars\" UI\n    revised_text_length: int,\n  }","operationId":"auto_improve_and_evaluate_api_jd_quality_auto_improve_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JDQualityRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/email/partnership-invitation":{"post":{"tags":["Email"],"summary":"Send Partnership Invitation","description":"Send partnership invitation email\n\nThis endpoint is called by Next.js after creating the invitation in the database.\nIt sends the actual email with the magic link.","operationId":"send_partnership_invitation_api_email_partnership_invitation_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnershipInvitationEmailRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/send-email":{"post":{"tags":["Email"],"summary":"Send Generic Email","description":"Send a candidate-facing email (screening invite, status update, etc.).\n\nRoutes through `_send()` so the Resend / FastMail switch, Reply-To,\nList-Unsubscribe header, and the new `interview@` From address all\napply uniformly with the rest of the email surface.","operationId":"send_generic_email_api_send_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenericEmailRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/parse-resume":{"post":{"tags":["candidates"],"summary":"Parse Resume Text","description":"Parse resume text and extract structured data using LLM.\n\nRate-limited (untrusted text → per-call LLM cost). The actual parsing\nlives in `_parse_resume_impl` so the file-upload path can reuse it\nwithout going through the HTTP rate limiter.","operationId":"parse_resume_text_api_candidates_parse_resume_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseResumeTextRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParsedResume"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/parse-resume-file":{"post":{"tags":["candidates"],"summary":"Parse Resume File","description":"Parse uploaded resume file (PDF, DOCX, TXT)\nUses hybrid approach: pdfplumber/PyPDF2 first (fast, free),\nthen DeepSeek Files API fallback for problematic PDFs\n\nArgs:\n    file: Resume file upload\n\nReturns:\n    dict: Contains raw_text and parsed_resume","operationId":"parse_resume_file_api_candidates_parse_resume_file_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_parse_resume_file_api_candidates_parse_resume_file_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_email}/progress":{"get":{"tags":["candidates"],"summary":"Get Candidate Progress","description":"Get candidate's progress across interviews with trend analysis\n\nArgs:\n    candidate_email: Candidate's email address\n    limit: Maximum number of interviews to retrieve (default: 10)\n\nReturns:\n    Progress data with:\n    - interviews: List of interviews with metrics\n    - trends: Improvement trends for each metric\n    - summary: Overall progress summary with recommendations\n\nPhase 1 Feature: Communication Metrics & Progress Tracking","operationId":"get_candidate_progress_api_candidates__candidate_email__progress_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_email","in":"path","required":true,"schema":{"type":"string","title":"Candidate Email"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_email}/metrics-history":{"get":{"tags":["candidates"],"summary":"Get Metrics History","description":"Get full metrics history for candidate\n\nArgs:\n    candidate_email: Candidate's email address\n    limit: Maximum number of records to retrieve (default: 20)\n\nReturns:\n    List of historical metrics with interview details\n\nPhase 1 Feature: Communication Metrics & Progress Tracking","operationId":"get_metrics_history_api_candidates__candidate_email__metrics_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_email","in":"path","required":true,"schema":{"type":"string","title":"Candidate Email"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/search":{"get":{"tags":["candidates"],"summary":"Search Candidates","description":"Search candidates by name, email, or phone for autocomplete\n\nArgs:\n    q: Search query (matches name, email, or phone — substring)\n    limit: Maximum results to return\n\nReturns:\n    List of matching candidates","operationId":"search_candidates_api_candidates_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/list":{"get":{"tags":["candidates"],"summary":"List Candidates","description":"List all candidates for the organization\n\nArgs:\n    status_filter: Filter by status ('active', 'hired', 'rejected')\n    search: Search by name or email\n    hiring_company: Filter to candidates with ≥1 interview against a\n        role whose hiring_company_display matches (trim + case-\n        insensitive). Drives the agency-mode \"show me Tesco\n        candidates\" view. Single value for v1 — multi-select is a\n        Phase 2 question if a customer asks.\n    limit: Maximum results per page\n    offset: Pagination offset\n\nReturns:\n    Paginated list of candidates with interview counts","operationId":"list_candidates_api_candidates_list_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"hiring_company","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company"}},{"name":"mine","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Mine"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/details":{"get":{"tags":["candidates"],"summary":"Get Candidate Details","description":"Get detailed candidate information including all interviews\n\nArgs:\n    candidate_id: Candidate UUID\n\nReturns:\n    Candidate details with interview history","operationId":"get_candidate_details_api_candidates__candidate_id__details_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/create":{"post":{"tags":["candidates"],"summary":"Create Candidate","description":"Create a new candidate\n\nArgs:\n    request: Candidate creation data\n\nReturns:\n    Created candidate","operationId":"create_candidate_api_candidates_create_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandidateCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/candidates/{candidate_id}/update":{"put":{"tags":["candidates"],"summary":"Update Candidate","description":"Update an existing candidate\n\nArgs:\n    candidate_id: Candidate UUID\n    request: Fields to update\n\nReturns:\n    Updated candidate","operationId":"update_candidate_api_candidates__candidate_id__update_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandidateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/resume-download":{"get":{"tags":["candidates"],"summary":"Download Candidate Resume","description":"Short-lived presigned URL for the candidate's stored resume.\n\nThe resume lives in S3 at candidates.resume_path; the FE opens the\nreturned URL directly (no proxying of file bytes through the BE).","operationId":"download_candidate_resume_api_candidates__candidate_id__resume_download_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/interviews":{"get":{"tags":["candidates"],"summary":"Get Candidate Interviews","description":"Get all interviews for a candidate (for Schedule Next Round feature)\n\nArgs:\n    candidate_id: Candidate UUID\n\nReturns:\n    Candidate info and all interviews","operationId":"get_candidate_interviews_api_candidates__candidate_id__interviews_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/upload-resume":{"post":{"tags":["candidates"],"summary":"Upload Candidate Resume","description":"Upload and store a resume for a candidate","operationId":"upload_candidate_resume_api_candidates__candidate_id__upload_resume_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_candidate_resume_api_candidates__candidate_id__upload_resume_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/resolve":{"post":{"tags":["candidates"],"summary":"Resolve Candidate","description":"Resolve candidate email → UUID. Creates the candidate record if it doesn't exist.\nReturns: { \"id\": \"uuid\", \"name\": \"...\", \"email\": \"...\", \"status\": \"...\" }","operationId":"resolve_candidate_api_candidates_resolve_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveCandidateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/candidates/{candidate_id}/decision-summary":{"get":{"tags":["candidates"],"summary":"Get Candidate Decision Summary","description":"Get everything the Decision page needs for a candidate.\nAccepts candidate UUID. Returns interviews with scores + aggregated debrief (if ≥2 human interviews).","operationId":"get_candidate_decision_summary_api_candidates__candidate_id__decision_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/decision":{"post":{"tags":["candidates"],"summary":"Record Candidate Decision","description":"Record a hiring decision for a candidate (Advance/Hold/Pass).\nAccepts candidate UUID. Updates candidate status directly.","operationId":"record_candidate_decision_api_candidates__candidate_id__decision_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandidateDecisionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/debrief/report":{"get":{"tags":["candidates"],"summary":"Get Candidate Debrief Report","description":"Download the debrief PDF for a candidate by UUID.\nUses cookie-based auth (internal dashboard use). Requires ≥2 completed human interviews.","operationId":"get_candidate_debrief_report_api_candidates__candidate_id__debrief_report_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/pipelines":{"get":{"tags":["candidates"],"summary":"Get Candidate Pipelines","description":"Returns every job role this candidate is in the pipeline for, with the\ncandidate's per-role context (stage, fit score, interview count, latest\ninterview). Powers the \"Roles in pipeline\" card on the candidate detail\npage.\n\nSource-of-truth is the job_role_candidates junction table — that's the\nonly signal that means \"this candidate was *intentionally* attached to\nthis role\" (a candidate could appear in interviews on a role they were\nnever sourced for, e.g., if a recruiter hand-created an interview row\nbypassing the candidates tab; we ignore that edge case here).","operationId":"get_candidate_pipelines_api_candidates__candidate_id__pipelines_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/pipelines/{job_role_id}/match-details":{"get":{"tags":["candidates"],"summary":"Get Pipeline Match Details","description":"Returns the rich resume↔JD breakdown for a single (candidate, role)\npairing — strengths, concerns, requirements checklist, summary,\nrecommendation. Powers the inline expand panel on the candidate\ndetail page and the candidates tab on the role page.\n\nReads from `match_results` (the cache populated by core.fit_scoring)\nvia `job_role_candidates.match_id`. No LLM call — this is purely a\njoin + projection. Returns 404 when the pairing has no scored match\nyet so the UI can render an \"awaiting score\" state.","operationId":"get_pipeline_match_details_api_candidates__candidate_id__pipelines__job_role_id__match_details_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}},{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","title":"Job Role Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/pipelines/{job_role_id}/rescore-fit":{"post":{"tags":["candidates"],"summary":"Rescore Pipeline Fit","description":"Manual recompute trigger for a single (candidate, role) pairing.\nUsed by the FitScoreExpand \"awaiting\" state and as the backstop for\npairings that pre-date the auto-trigger work (where neither\nadd-candidate nor resume-upload nor JD-update has fired since).\n\nForces re-evaluation regardless of whether the resume hash matches\nthe previously-scored hash — the manual button is the user\nexplicitly saying \"I want a fresh number.\" The match_results cache\nstill applies (same JD + same resume returns instantly), so\nrepeated clicks are cheap.","operationId":"rescore_pipeline_fit_api_candidates__candidate_id__pipelines__job_role_id__rescore_fit_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}},{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","title":"Job Role Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/generate-profile-summary":{"post":{"tags":["candidates"],"summary":"Generate Candidate Profile Summary","description":"Returns a 2-3 sentence AI-generated brief derived from the candidate's\nresume. Cached on the candidates row; regenerate by passing ?force=true\n(e.g., from a \"Regenerate\" button after the resume changes).\n\nThe brief is a triage tool — a recruiter glancing at the candidate page\nshould know in 2 seconds whether this person is worth the next click.\nKeep the prompt strict on length and style.","operationId":"generate_candidate_profile_summary_api_candidates__candidate_id__generate_profile_summary_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/compare/resolve":{"post":{"tags":["compare-internal"],"summary":"Resolve Candidates For Compare","description":"Resolve candidate emails → candidate_ids for the compare page.\n\nREAD-ONLY on purpose — unknown emails are silently skipped, never created\n(unlike candidates.py /resolve, whose find_or_create semantics would mint\nphantom candidate rows from typo'd emails here). Falls back to the email on\nthe candidate's interviews when no candidates row exists. Org comes from\nthe token — this replaces a Next.js route that trusted a spoofable\nX-Organization-ID header (API proxy sweep).","operationId":"resolve_candidates_for_compare_api_compare_resolve_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveEmailsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/compare":{"post":{"tags":["compare-internal"],"summary":"Compare Candidates Internal","description":"Dashboard-facing compare endpoint. Cookie-authenticated.","operationId":"compare_candidates_internal_api_compare_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalCompareRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/ai-review/interview/{interview_id}":{"get":{"tags":["ai-review"],"summary":"Get Interview Ai Review","description":"Stored AI review for an interview's metadata.\n\nSECURITY: the old Next.js route (app/api/interviews/[id]/ai-review) was\nUNAUTHENTICATED and scoped only by interview id — anyone with an interview\nUUID could read candidate name/email + the AI review across ANY org. This\nendpoint requires auth and scopes by the caller's organization_id.","operationId":"get_interview_ai_review_api_ai_review_interview__interview_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/competencies/library":{"get":{"tags":["competencies"],"summary":"Get Competency Library","description":"Get all competencies available to the organization.\nIncludes system defaults (is_system_default=true, org_id=NULL)\nand organization-specific competencies.","operationId":"get_competency_library_api_competencies_library_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by category","title":"Category"},"description":"Filter by category"},{"name":"include_system","in":"query","required":false,"schema":{"type":"boolean","description":"Include system default competencies","default":true,"title":"Include System"},"description":"Include system default competencies"},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","description":"Include inactive competencies","default":false,"title":"Include Inactive"},"description":"Include inactive competencies"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetencyListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["competencies"],"summary":"Create Competency","description":"Create a new custom competency for the organization.","operationId":"create_competency_api_competencies_library_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetencyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetencyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/competencies/library/{competency_id}":{"put":{"tags":["competencies"],"summary":"Update Competency","description":"Update an organization's custom competency.\nCannot update system default competencies.","operationId":"update_competency_api_competencies_library__competency_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"competency_id","in":"path","required":true,"schema":{"type":"string","title":"Competency Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetencyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetencyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["competencies"],"summary":"Delete Competency","description":"Soft delete an organization's custom competency.\nCannot delete system default competencies.","operationId":"delete_competency_api_competencies_library__competency_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"competency_id","in":"path","required":true,"schema":{"type":"string","title":"Competency Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/competencies/stages/{stage_id}/competencies":{"get":{"tags":["competencies"],"summary":"Get Stage Competencies","description":"Get competencies configured for a specific interview stage.","operationId":"get_stage_competencies_api_competencies_stages__stage_id__competencies_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"stage_id","in":"path","required":true,"schema":{"type":"string","title":"Stage Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["competencies"],"summary":"Update Stage Competencies","description":"Update competencies for a specific interview stage.\nAccepts both library competency IDs and custom ad-hoc competencies.","operationId":"update_stage_competencies_api_competencies_stages__stage_id__competencies_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"stage_id","in":"path","required":true,"schema":{"type":"string","title":"Stage Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StageCompetencyConfig"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/competencies/interviews/{interview_id}/competencies":{"get":{"tags":["competencies"],"summary":"Get Interview Competencies","description":"Get effective competencies for a specific interview.\nResolves from interview override → stage defaults → job role defaults.","operationId":"get_interview_competencies_api_competencies_interviews__interview_id__competencies_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["competencies"],"summary":"Update Interview Competencies","description":"Set competency override for a specific interview.\nThis is Layer 3 - highest priority in resolution.","operationId":"update_interview_competencies_api_competencies_interviews__interview_id__competencies_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewCompetencyOverride"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["competencies"],"summary":"Clear Interview Competency Override","description":"Remove competency override from an interview.\nInterview will use stage defaults again.","operationId":"clear_interview_competency_override_api_competencies_interviews__interview_id__competencies_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/competencies/resolve/{interview_id}":{"get":{"tags":["competencies"],"summary":"Resolve Competencies For Analysis","description":"Resolve the final list of competencies to use for interview analysis.\nReturns fully hydrated competency objects ready for the analysis pipeline.\n\nResolution order (highest to lowest priority):\n1. Interview-level override (metadata.competencies_override)\n2. Stage-level defaults (interview_stages.competencies_to_evaluate)\n3. Job role defaults (job_roles.competencies_config)","operationId":"resolve_competencies_for_analysis_api_competencies_resolve__interview_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/frameworks":{"get":{"tags":["frameworks"],"summary":"List Frameworks","description":"List all available interview frameworks.","operationId":"list_frameworks_api_frameworks_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","description":"Include inactive frameworks","default":false,"title":"Include Inactive"},"description":"Include inactive frameworks"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FrameworkListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/frameworks/{code}":{"get":{"tags":["frameworks"],"summary":"Get Framework","description":"Get details of a specific framework by code.","operationId":"get_framework_api_frameworks__code__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FrameworkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/frameworks/{code}/questions":{"get":{"tags":["frameworks"],"summary":"Get Framework Questions","description":"Get questions for a specific framework.\nReturns both system defaults and organization-specific questions.","operationId":"get_framework_questions_api_frameworks__code__questions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by category","title":"Category"},"description":"Filter by category"},{"name":"career_phase","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by career phase (Topgrading)","title":"Career Phase"},"description":"Filter by career phase (Topgrading)"},{"name":"competency_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by competency","title":"Competency Id"},"description":"Filter by competency"},{"name":"include_system","in":"query","required":false,"schema":{"type":"boolean","description":"Include system default questions","default":true,"title":"Include System"},"description":"Include system default questions"},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","description":"Include inactive questions","default":false,"title":"Include Inactive"},"description":"Include inactive questions"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/frameworks/questions":{"post":{"tags":["frameworks"],"summary":"Create Question","description":"Create a new custom question for the organization.","operationId":"create_question_api_frameworks_questions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/frameworks/questions/{question_id}":{"put":{"tags":["frameworks"],"summary":"Update Question","description":"Update an organization's custom question.\nCannot update system default questions.","operationId":"update_question_api_frameworks_questions__question_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"question_id","in":"path","required":true,"schema":{"type":"string","title":"Question Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["frameworks"],"summary":"Delete Question","description":"Soft delete an organization's custom question.\nCannot delete system default questions.","operationId":"delete_question_api_frameworks_questions__question_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"question_id","in":"path","required":true,"schema":{"type":"string","title":"Question Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/frameworks/questions/{question_id}/increment-usage":{"post":{"tags":["frameworks"],"summary":"Increment Question Usage","description":"Increment the usage count for a question (when used in an interview).","operationId":"increment_question_usage_api_frameworks_questions__question_id__increment_usage_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"question_id","in":"path","required":true,"schema":{"type":"string","title":"Question Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/frameworks/interviews/{interview_id}":{"get":{"tags":["frameworks"],"summary":"Get Interview Framework","description":"Get the framework assigned to an interview.\nResolves from: interview > stage default > null (legacy)","operationId":"get_interview_framework_api_frameworks_interviews__interview_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["frameworks"],"summary":"Set Interview Framework","description":"Set or update the framework for an interview.","operationId":"set_interview_framework_api_frameworks_interviews__interview_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}},{"name":"framework_code","in":"query","required":true,"schema":{"type":"string","description":"Framework code (bei, cbi, topgrading)","title":"Framework Code"},"description":"Framework code (bei, cbi, topgrading)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["frameworks"],"summary":"Clear Interview Framework","description":"Clear the framework override from an interview.\nInterview will use stage default (if any) or legacy analysis.","operationId":"clear_interview_framework_api_frameworks_interviews__interview_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/frameworks/stages/{stage_id}/default":{"put":{"tags":["frameworks"],"summary":"Set Stage Default Framework","description":"Set the default framework for an interview stage.\nAll interviews in this stage will use this framework unless overridden.","operationId":"set_stage_default_framework_api_frameworks_stages__stage_id__default_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"stage_id","in":"path","required":true,"schema":{"type":"string","title":"Stage Id"}},{"name":"framework_code","in":"query","required":true,"schema":{"type":"string","description":"Framework code (bei, cbi, topgrading)","title":"Framework Code"},"description":"Framework code (bei, cbi, topgrading)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["frameworks"],"summary":"Clear Stage Default Framework","description":"Clear the default framework from an interview stage.","operationId":"clear_stage_default_framework_api_frameworks_stages__stage_id__default_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"stage_id","in":"path","required":true,"schema":{"type":"string","title":"Stage Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/frameworks/resolve/{interview_id}":{"get":{"tags":["frameworks"],"summary":"Resolve Framework For Analysis","description":"Resolve the effective framework for interview analysis.\nReturns framework config including prompt template, evaluation criteria, and report sections.\n\nResolution order:\n1. Interview-level override (interviews.framework_code)\n2. Stage-level default (interview_stages.default_framework_code)\n3. Null (use legacy analysis)","operationId":"resolve_framework_for_analysis_api_frameworks_resolve__interview_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/generate-prep-questions":{"post":{"tags":["interview-prep"],"summary":"Generate Prep Questions","description":"Generate AI-powered interview preparation questions.\n\nPhase C: this endpoint is now a thin adapter over\n`core.question_bank.prep_runner.run_prep_generation` so the same\ngeneration logic powers both this manual trigger and the post-round\nauto-trigger that fires from the screening / brain / human pipelines.\nSide effects (interviews.prep_questions persist + bank push) live in\nthe runner.","operationId":"generate_prep_questions_api_interviews__interview_id__generate_prep_questions_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepQuestionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interviews/{interview_id}/prep-questions":{"get":{"tags":["interview-prep"],"summary":"Get Prep Questions","description":"Get existing prep questions for an interview","operationId":"get_prep_questions_api_interviews__interview_id__prep_questions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/candidates/{candidate_id}/question-bank":{"get":{"tags":["question-bank"],"summary":"Get Question Bank","operationId":"get_question_bank_api_candidates__candidate_id__question_bank_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Candidate Id"}},{"name":"job_role_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","description":"The job role this bank slice is anchored to.","title":"Job Role Id"},"description":"The job role this bank slice is anchored to."},{"name":"include_resolved","in":"query","required":false,"schema":{"type":"boolean","description":"Include asked / answered / skipped / superseded entries. Default returns only open.","default":false,"title":"Include Resolved"},"description":"Include asked / answered / skipped / superseded entries. Default returns only open."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/question-bank/{entry_id}/status":{"post":{"tags":["question-bank"],"summary":"Update Status","operationId":"update_status_api_question_bank__entry_id__status_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Entry Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetStatusRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankEntryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/pricing":{"get":{"tags":["billing"],"summary":"Get Pricing","description":"Bundle pricing v1 — public, no auth.\n\nReturns the standard rates for everyone (founding overrides apply per-org\nvia /balance for authenticated callers). Used by marketing pages and the\npricing-drift CI check that compares against lib/billing-constants.ts.","operationId":"get_pricing_api_billing_pricing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingResponse"}}}}}}},"/api/billing/create-checkout-session":{"post":{"tags":["billing"],"summary":"Create Checkout","description":"Create a Stripe Checkout session or modify an existing subscription.\n\nIf the organization already has an active subscription, modify it in-place\n(preventing duplicate subscriptions). Otherwise, create a new checkout session.\nOnly admins can initiate checkout.","operationId":"create_checkout_api_billing_create_checkout_session_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/billing/create-portal-session":{"post":{"tags":["billing"],"summary":"Create Portal","description":"Create a Stripe Customer Portal session.\nOnly admins can access billing portal.","operationId":"create_portal_api_billing_create_portal_session_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalSessionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/billing/webhook":{"post":{"tags":["billing"],"summary":"Stripe Webhook","description":"Handle Stripe webhook events.\nThis endpoint is called by Stripe - no auth required but signature verified.","operationId":"stripe_webhook_api_billing_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/billing/balance":{"get":{"tags":["billing"],"summary":"Get Credit Balance","description":"Current credit balance + this org's bundle pricing + lifetime totals.","operationId":"get_credit_balance_api_billing_balance_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalanceResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/billing/candidates/{candidate_id}/total":{"get":{"tags":["billing"],"summary":"Get Candidate Total","description":"Running pipeline cost for one candidate. Drives the per-candidate\ncost widget on the candidate / interview detail pages.","operationId":"get_candidate_total_api_billing_candidates__candidate_id__total_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandidateRunningTotalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/topup":{"post":{"tags":["billing"],"summary":"Create Topup","description":"Create a Stripe Checkout session for a one-time credit top-up.","operationId":"create_topup_api_billing_topup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopUpRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/billing/transactions":{"get":{"tags":["billing"],"summary":"List Credit Transactions","description":"Paginated credit transaction history, newest first. Cursor = created_at ISO string.","operationId":"list_credit_transactions_api_billing_transactions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Before"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/voice/{provider}":{"post":{"tags":["webhooks"],"summary":"Voice Webhook","description":"Unified webhook endpoint for all voice providers.\n\nEach provider is configured to send webhooks to their specific path:\n- Vapi: /api/webhooks/voice/vapi\n- Bland: /api/webhooks/voice/bland\n- Retell: /api/webhooks/voice/retell\n\nArgs:\n    provider: The provider name from URL path\n    request: FastAPI request object\n\nReturns:\n    {\"received\": True} on success","operationId":"voice_webhook_api_webhooks_voice__provider__post","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/vapi":{"post":{"tags":["webhooks"],"summary":"Vapi Webhook Legacy","description":"Legacy Vapi webhook endpoint.\nRedirects to unified handler for backward compatibility.","operationId":"vapi_webhook_legacy_api_webhooks_vapi_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/webhooks/bot/{provider}":{"post":{"tags":["webhooks"],"summary":"Bot Webhook","description":"Unified webhook endpoint for all meeting-bot providers.","operationId":"bot_webhook_api_webhooks_bot__provider__post","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/email/resend":{"post":{"tags":["webhooks"],"summary":"Resend Email Webhook","description":"Receive a Resend email event and relay engagement to Mixpanel.\n\nAlways 200 once the signature checks out — Resend should not retry on our\naccount; the relay is best-effort. 400 only for an unverifiable request.","operationId":"resend_email_webhook_api_webhooks_email_resend_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/voice/brain-llm/{interview_id}/chat/completions":{"post":{"tags":["voice","voice"],"summary":"Brain Llm","description":"Vapi-shaped chat-completions endpoint backed by the brain orchestrator.\n\nPath note: Vapi's `custom-llm` provider treats the configured `model.url`\nas a BASE URL and appends `/chat/completions` (OpenAI convention) when\nPOSTing turn payloads. So the URL we pass in assistant_config is\n`https://.../api/v1/voice/brain-llm/{id}` and Vapi actually hits\n`.../brain-llm/{id}/chat/completions`. This route's path matches that.\n\nStreaming contract — the response starts BEFORE the brain turn finishes:\n  delta(role=assistant) -> [keepalive chunks while the turn runs] ->\n  delta(content=<next_question>) -> finish_reason=stop\n\nv0.2.17 (Batch A / Stage 2 — A3): the close path (finished /\nerror / timeout) emits the same shape — delta(content) →\nfinish_reason=stop — instead of the previous tool_calls=[{name:\n\"endCall\"}] tail that cut TTS mid-sentence. Vapi terminates the\ncall via `endCallPhrases` (configured in interview_join.py)\nmatched against the brain's natural close phrasing, waiting for\nthe matched-phrase TTS to finish playing before hanging up.","operationId":"brain_llm_api_v1_voice_brain_llm__interview_id__chat_completions_post","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/_ChatCompletionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screening-questions/library":{"get":{"tags":["screening-questions"],"summary":"Get Screening Questions","description":"Get all screening questions available to the organization.\nIncludes system defaults and organization-specific questions.","operationId":"get_screening_questions_api_screening_questions_library_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by category","title":"Category"},"description":"Filter by category"},{"name":"include_system","in":"query","required":false,"schema":{"type":"boolean","description":"Include system default questions","default":true,"title":"Include System"},"description":"Include system default questions"},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","description":"Include inactive questions","default":false,"title":"Include Inactive"},"description":"Include inactive questions"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningQuestionListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["screening-questions"],"summary":"Create Screening Question","description":"Create a new custom screening question for the organization.","operationId":"create_screening_question_api_screening_questions_library_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningQuestionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningQuestionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screening-questions/library/{question_id}":{"put":{"tags":["screening-questions"],"summary":"Update Screening Question","description":"Update an organization's custom screening question.\nCannot update system default questions.","operationId":"update_screening_question_api_screening_questions_library__question_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"question_id","in":"path","required":true,"schema":{"type":"string","title":"Question Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningQuestionUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningQuestionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["screening-questions"],"summary":"Delete Screening Question","description":"Soft delete an organization's custom screening question.\nCannot delete system default questions.","operationId":"delete_screening_question_api_screening_questions_library__question_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"question_id","in":"path","required":true,"schema":{"type":"string","title":"Question Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screening-questions/library/reorder":{"post":{"tags":["screening-questions"],"summary":"Reorder Screening Questions","description":"Reorder screening questions by providing an ordered list of question IDs.\nOnly affects questions belonging to the organization.","operationId":"reorder_screening_questions_api_screening_questions_library_reorder_post","requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Question Ids"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/screening-questions/resolve":{"post":{"tags":["screening-questions"],"summary":"Resolve Screening Questions","description":"Resolve question IDs to full question objects.\nUsed when loading interview with selected questions.","operationId":"resolve_screening_questions_api_screening_questions_resolve_post","requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Question Ids"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/interview/join/{token}":{"get":{"tags":["interview-join"],"summary":"Get Interview Info","description":"Get interview details for candidate.\n\nThis is a PUBLIC endpoint - no authentication required.\nOnly returns non-sensitive information needed for the join page.","operationId":"get_interview_info_api_interview_join__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewJoinInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview/join/{token}/resume":{"post":{"tags":["interview-join"],"summary":"Upload Campaign Resume","description":"Campaign candidate provides their resume on the lobby (before Start) —\na FILE or PASTED TEXT. Replaces the interview's synthetic resume_text with\nthe real thing (so the brain personalizes the opener) and stores the\nparsed structure on the candidate so it shows in the recruiter's\ndashboard. Both writes matter: without the second, has_resume stays false\nand the recruiter never sees it.\n\nPaste is supported because this endpoint absorbed the /screen page's\nresume step — that page took pasted text, and dropping the option would\nhave shut out candidates who don't have a file to hand.\n\nPublic + token-scoped, and GATED to campaign screenings only\n(metadata.source == 'campaign_screen') — a normal interview's token can\nnever reach this write path.","operationId":"upload_campaign_resume_api_interview_join__token__resume_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"allOf":[{"$ref":"#/components/schemas/Body_upload_campaign_resume_api_interview_join__token__resume_post"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview/join/{token}/schedule-info":{"get":{"tags":["interview-join"],"summary":"Get Schedule Info","description":"Get interview info for the scheduling page.\n\nPUBLIC endpoint - used by candidates to see job details\nbefore picking a time slot.","operationId":"get_schedule_info_api_interview_join__token__schedule_info_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview/join/{token}/schedule":{"post":{"tags":["interview-join"],"summary":"Schedule Interview","description":"Candidate self-schedules their screening interview.\n\nPUBLIC endpoint - updates the interview with the candidate's chosen time.","operationId":"schedule_interview_api_interview_join__token__schedule_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__interview_join__ScheduleInterviewRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__interview_join__ScheduleInterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview/join/{token}/start":{"post":{"tags":["interview-join"],"summary":"Start Interview Call","description":"Start an interview for a candidate joining via {token}.\n\nBranches on interviewer_type:\n  - \"brain\"  → start a brain session via core.brain_session_runner and\n                return StartBrainResponse. The candidate page then runs\n                the text + voice loop against /brain/turn.\n  - \"human\" / null / Vapi screening → return StartCallResponse with the\n                Vapi assistant config.\n\nThis is a PUBLIC endpoint - no authentication required.","operationId":"start_interview_call_api_interview_join__token__start_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/StartConsentRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview/join/{token}/end":{"post":{"tags":["interview-join"],"summary":"End Interview Call","description":"Mark the interview as ended — 'completed' if it ran, 'failed' if it didn't.\n\nCalled when the candidate ends the call or the call ends naturally. The\ncandidate page posts {\"ai_spoke\": bool}: a screening where the AI never\nsaid a word is a dead call (e.g. the voice provider failed to start), not\na finished interview. Marking it 'failed' keeps the candidate's status\nsurfaces honest instead of leaving a zombie 'completed' with no transcript\nor score. The Vapi end-of-call webhook stays authoritative when it fires;\nthis also covers calls that died before any webhook arrived at all.","operationId":"end_interview_call_api_interview_join__token__end_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview/join/{token}/coding/problem":{"get":{"tags":["interview-join"],"summary":"Get Coding Problem","description":"Fetch the candidate-safe coding problem for this interview's\ncoding SKILL round. The FE polls this every few seconds while in the\ncall; the editor opens automatically the moment this returns 200.\n\n204 means \"no editor right now\" — the FE stays in voice mode.\nIndependent 204 paths:\n  - The interview has no job_role_id attached.\n  - interviewer_type != 'brain'.\n  - The interview is not a skill round (metadata.skill absent).\n  - The round's skill has no `tool=\"code_editor\"` stage — product_sense,\n    system_design and the other five non-coding skills never get an editor.\n  - The brain is not yet at the intro / code_editor tool stage.\n  - The codeval sandbox preflight failed (fail-before-hand-off —\n    candidate never sees a broken editor).","operationId":"get_coding_problem_api_interview_join__token__coding_problem_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview/join/{token}/coding/run":{"post":{"tags":["interview-join"],"summary":"Post Coding Run","description":"ADR-0004 §2b: on-demand snapshot evaluation. Candidate hits Run;\nFE shows `n/m visible tests passed`. The brain may also probe\nmid-attempt against the same endpoint (no side effects beyond the\nevaluation itself).\n\n`activity` is accepted but unused at this hop — it's bundled into\nthe terminal signal at /finish, where codeval derives anti-cheat\nsignals from the cumulative blob.","operationId":"post_coding_run_api_interview_join__token__coding_run_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodingRunRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/interview/join/{token}/coding/finish":{"post":{"tags":["interview-join"],"summary":"Post Coding Finish","description":"Terminal event for the candidate's attempt in a coding SKILL\nround. Assembles a BrainSignalEvent via codeval `/signal` and\ndelivers it to brain `/internal/brain-signal`, where it lands on\n`SessionState.specialist_events` (SkillStrategy advances the\ncode_editor stage on it; the judge scores the artifact at\nfinalize). `activity` is forwarded opaquely to codeval; the brain\nnever sees it.","operationId":"post_coding_finish_api_interview_join__token__coding_finish_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CodingFinishRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/meeting-join/{token}":{"post":{"tags":["meeting-join"],"summary":"Meeting Join","description":"Resolve the wrapper token, on-demand-launch the bot, return the real URL.\n\nThe read → guard → launch runs inside a transaction holding a per-token\nadvisory lock, so two near-simultaneous clicks (candidate + interviewer at\nthe same second) can't both pass the guard and launch duplicate Recall bots:\nthe winner launches, a concurrent loser sees the winner's bot (or bails on\nthe lock) and just redirects. The lock key is the token, so different\ninterviews never block each other.","operationId":"meeting_join_api_meeting_join__token__post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeetingJoinResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/debrief/generate-summary":{"post":{"tags":["debrief"],"summary":"Generate Executive Summary","description":"Generate AI executive summary for a debrief report\n\nArgs:\n    request: ExecutiveSummaryRequest with aggregated interview data\n\nReturns:\n    ExecutiveSummaryResponse with the generated summary","operationId":"generate_executive_summary_api_debrief_generate_summary_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecutiveSummaryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecutiveSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/test-data/generate-jd":{"post":{"tags":["test-data"],"summary":"Generate Jd","description":"Generate a synthetic job description.","operationId":"generate_jd_api_test_data_generate_jd_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__test_data__GenerateJDRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/test-data/generate-resume":{"post":{"tags":["test-data"],"summary":"Generate Resume","description":"Generate a synthetic resume that targets the provided JD.","operationId":"generate_resume_api_test_data_generate_resume_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResumeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/test-data/generate-transcript":{"post":{"tags":["test-data"],"summary":"Generate Transcript","description":"Generate a synthetic interview transcript.","operationId":"generate_transcript_api_test_data_generate_transcript_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateTranscriptRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/test-data/generate-audio":{"post":{"tags":["test-data"],"summary":"Generate Audio","description":"Generate TTS audio from a transcript JSON.","operationId":"generate_audio_api_test_data_generate_audio_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateAudioRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/test-data/pipelines":{"get":{"tags":["test-data"],"summary":"List Pipelines","description":"List saved test lab pipelines for the organization.","operationId":"list_pipelines_api_test_data_pipelines_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["test-data"],"summary":"Save Pipeline","description":"Save a test lab pipeline run to the database.","operationId":"save_pipeline_api_test_data_pipelines_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavePipelineRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/test-data/pipelines/{pipeline_id}":{"put":{"tags":["test-data"],"summary":"Update Pipeline","description":"Update an existing test lab pipeline.","operationId":"update_pipeline_api_test_data_pipelines__pipeline_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavePipelineRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["test-data"],"summary":"Get Pipeline","description":"Get full pipeline data including artifacts for downloads.","operationId":"get_pipeline_api_test_data_pipelines__pipeline_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["test-data"],"summary":"Delete Pipeline","description":"Delete a saved pipeline.","operationId":"delete_pipeline_api_test_data_pipelines__pipeline_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/test-data/pipelines/{pipeline_id}/upload-audio":{"post":{"tags":["test-data"],"summary":"Upload Pipeline Audio","description":"Upload audio file for a saved pipeline.","operationId":"upload_pipeline_audio_api_test_data_pipelines__pipeline_id__upload_audio_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_pipeline_audio_api_test_data_pipelines__pipeline_id__upload_audio_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/test-data/pipelines/{pipeline_id}/audio":{"get":{"tags":["test-data"],"summary":"Stream Pipeline Audio","description":"Stream pipeline audio file over HTTP","operationId":"stream_pipeline_audio_api_test_data_pipelines__pipeline_id__audio_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/test-data/pipelines/{pipeline_id}/inject":{"post":{"tags":["test-data"],"summary":"Inject Pipeline","description":"Inject a saved pipeline into the real interview system.","operationId":"inject_pipeline_api_test_data_pipelines__pipeline_id__inject_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InjectPipelineRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/interviews":{"post":{"tags":["Public API v1","Public API v1"],"summary":"Submit interview for analysis","description":"Submit an interview for AI-powered analysis. Two input modes:\n\n**Mode 1 (meeting link):** Provide `meeting_url` + `scheduled_at`. BetterHire bot joins the meeting, records, transcribes, and analyzes.\n\nRate limit: 50 requests/hour per API key.\n\n**Mode 2 (recording URL):** Provide `recording_url`. BetterHire transcribes and analyzes the existing recording.\n\nPoll GET /interviews/{id} or register a webhook to be notified when complete.","operationId":"submit_interview_api_public_v1_interviews_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewSubmitRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewSubmitResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Public API v1","Public API v1"],"summary":"List interviews","description":"List interviews for your organization with pagination. Filter by status, candidate email, role, or date range.","operationId":"list_interviews_api_public_v1_interviews_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by overall status: pending, processing, completed, failed.","title":"Status"},"description":"Filter by overall status: pending, processing, completed, failed."},{"name":"candidate_email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by candidate email address (exact match).","title":"Candidate Email"},"description":"Filter by candidate email address (exact match)."},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by role (case-insensitive partial match).","title":"Role"},"description":"Filter by role (case-insensitive partial match)."},{"name":"created_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter interviews created after this ISO 8601 datetime.","title":"Created After"},"description":"Filter interviews created after this ISO 8601 datetime."},{"name":"created_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter interviews created before this ISO 8601 datetime.","title":"Created Before"},"description":"Filter interviews created before this ISO 8601 datetime."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Number of results per page.","default":20,"title":"Limit"},"description":"Number of results per page."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Offset for pagination.","default":0,"title":"Offset"},"description":"Offset for pagination."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__public_api__InterviewListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/balance":{"get":{"tags":["Public API v1","Public API v1"],"summary":"Get credit wallet balance and bundle pricing","description":"Returns the current credit balance for the authenticated organization along with this org's bundle price, per-action extra rates, and lifetime totals. Use this to surface balance in MCP tools, dashboards, or partner integrations.","operationId":"get_balance_endpoint_api_public_v1_balance_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalanceResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/interviews/{interview_id}":{"get":{"tags":["Public API v1","Public API v1"],"summary":"Get interview status, scorecard, and transcript","description":"Retrieve the full interview analysis including scorecard with competency scores, timestamped evidence, action plan, communication metrics, and transcript. Evidence items include timestamps — use with recording_url for audio playback. The scorecard covers every interview kind: AI screenings, AI interviews and submitted recordings. Competencies the interview never reached are omitted from `competencies` and listed in `not_assessed`; `coverage_summary`, `score_confidence` and `transcript_quality` say how much of the rubric the score rests on. A completed interview with `scorecard: null` carries a top-level `transcript_quality` whose label ('partial' or 'invalid') says the candidate said too little to score, with the word count the gate measured.","operationId":"get_interview_api_public_v1_interviews__interview_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Public API v1","Public API v1"],"summary":"Delete interview (GDPR)","description":"DISABLED — interview deletion is currently turned off product-wide; this endpoint returns 403 DELETION_DISABLED. Use the dashboard's 'Mark invalid' to exclude an interview, or contact support@thebetterhire.ai for data-removal requests.","operationId":"delete_interview_api_public_v1_interviews__interview_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/interviews/{interview_id}/cancel":{"post":{"tags":["Public API v1","Public API v1"],"summary":"Cancel an interview nobody has started","description":"Close an interview the candidate has not started — they closed the tab, the microphone failed, they changed their mind. The credit set aside for it is released at once; nothing is charged; the record is kept (this is not deletion). Returns 409 `ALREADY_STARTED` once the candidate has pressed Start, and 409 `ALREADY_FINISHED` for a completed or failed interview. Cancelling twice is harmless.\n\nInterviews created through this API also close on their own one hour after they were meant to start (`join_expires_at` on the detail).","operationId":"cancel_interview_api_public_v1_interviews__interview_id__cancel_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelInterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/interviews/{interview_id}/report":{"get":{"tags":["Public API v1","Public API v1"],"summary":"Download interview report as PDF","description":"Generate and download a professional PDF report of the interview analysis. Includes overall score, competency scorecard with evidence, action plan, and communication metrics. Suitable for attaching to ATS candidate profiles or emailing to hiring panels.","operationId":"get_interview_report_api_public_v1_interviews__interview_id__report_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/interviews/{interview_id}/audio":{"get":{"tags":["Public API v1","Public API v1"],"summary":"Get playable audio URL","description":"Returns a time-limited presigned URL for the interview recording. The URL expires after 1 hour. Use with evidence timestamps from the scorecard (start_seconds, end_seconds) to seek to specific moments.\n\nExample: if evidence shows start_seconds=872.3, open the URL and seek to 14:32.\n\n**Clips.** Pass `start` and `end` (seconds) to get a URL for just that window — audio-only, cut server-side, cached, same one-hour life — instead of downloading the whole recording to play twenty seconds of it. Windows up to three minutes.","operationId":"get_interview_audio_api_public_v1_interviews__interview_id__audio_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Clip start, in seconds. Requires `end`.","title":"Start"},"description":"Clip start, in seconds. Requires `end`."},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"description":"Clip end, in seconds. Requires `start`.","title":"End"},"description":"Clip end, in seconds. Requires `start`."},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"For a clip: `audio` (default, an .m4a of the window) or `video` (a small .mp4 of the window, scaled down, for showing the candidate's face). Needs `start` and `end`.","default":"audio","title":"Format"},"description":"For a clip: `audio` (default, an .m4a of the window) or `video` (a small .mp4 of the window, scaled down, for showing the candidate's face). Needs `start` and `end`."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/interviews/{interview_id}/frame":{"get":{"tags":["Public API v1","Public API v1"],"summary":"Get a still of the candidate's face","description":"Returns a time-limited URL to one JPEG frame from a video interview in which the candidate's face is clearly visible: one face, large, sharp and well lit, picked from across the recording (the first 30 seconds only if nothing later qualifies). Always 1280x720. Picked on the first request (a few seconds) and cached; later requests are instant.\n\nCall it only when the candidate has agreed to show their face; nothing is picked unless asked. Detection only: the person is never identified and no face data is kept besides the image.\n\n**404 `NO_FACE_FRAME`** when the recording has no picture (an audio-only interview) or no frame qualifies. **404 `NO_RECORDING`** before the recording is saved. **503 `FRAME_UNAVAILABLE`**: retry.","operationId":"get_interview_face_frame_api_public_v1_interviews__interview_id__frame_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FaceFrameResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/webhooks":{"get":{"tags":["Public API v1 — Webhooks","Public API v1 — Webhooks"],"summary":"List webhooks","description":"List all registered webhook endpoints for your organization.","operationId":"list_webhooks_api_public_v1_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Public API v1 — Webhooks","Public API v1 — Webhooks"],"summary":"Register a webhook","description":"Register a URL to receive POST notifications when interview events occur. Returns a signing secret — store it securely. All webhook payloads are signed with HMAC-SHA256 using this secret.","operationId":"create_webhook_api_public_v1_webhooks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/webhooks/{webhook_id}":{"delete":{"tags":["Public API v1 — Webhooks","Public API v1 — Webhooks"],"summary":"Delete a webhook","description":"Soft-delete a webhook endpoint. It will stop receiving events immediately.","operationId":"delete_webhook_api_public_v1_webhooks__webhook_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/webhooks/{webhook_id}/deliveries":{"get":{"tags":["Public API v1 — Webhooks","Public API v1 — Webhooks"],"summary":"Get delivery history","description":"View recent delivery attempts for a webhook endpoint.","operationId":"get_deliveries_api_public_v1_webhooks__webhook_id__deliveries_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/candidates/{candidate_id}/debrief":{"get":{"tags":["Public API v1 — Debrief","Public API v1 — Candidates & Debrief"],"summary":"Get debrief by candidate ID","description":"Synthesizes all completed interviews for a candidate into a hiring decision report. Use candidate_id from POST /interviews response.","operationId":"get_debrief_by_id_api_public_v1_candidates__candidate_id__debrief_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DebriefResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/candidates/{candidate_id}/debrief/report":{"get":{"tags":["Public API v1 — Debrief","Public API v1 — Candidates & Debrief"],"summary":"Download debrief PDF by candidate ID","operationId":"get_debrief_report_by_id_api_public_v1_candidates__candidate_id__debrief_report_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/debrief":{"get":{"tags":["Public API v1 — Debrief","Public API v1 — Candidates & Debrief"],"summary":"Get debrief by candidate email","description":"Same as /candidates/{id}/debrief but looks up candidate by email. Use when you don't have the candidate_id stored.","operationId":"get_debrief_by_email_api_public_v1_debrief_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_email","in":"query","required":true,"schema":{"type":"string","title":"Candidate Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DebriefResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/debrief/report":{"get":{"tags":["Public API v1 — Debrief","Public API v1 — Candidates & Debrief"],"summary":"Download debrief PDF by candidate email","description":"Same as /candidates/{id}/debrief/report but looks up by email.","operationId":"get_debrief_report_by_email_api_public_v1_debrief_report_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"candidate_email","in":"query","required":true,"schema":{"type":"string","title":"Candidate Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/compare":{"post":{"tags":["Public API v1 — Comparison","Public API v1 — Comparison"],"summary":"Compare candidates side by side","description":"Compare 2-4 candidates with overall ranking, competency matrix, head-to-head on overlapping rounds, communication comparison, and risk assessment. Handles candidates at different interview stages with confidence flags and warnings.","operationId":"compare_candidates_endpoint_api_public_v1_compare_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/screening":{"post":{"tags":["Public API v1 — Screening","Public API v1 — AI Screening"],"summary":"Trigger AI screening interview","description":"Launch an AI-powered screening interview for a candidate. Two modes:\n\n**Phone mode:** AI bot calls the candidate's phone immediately. Requires `candidate_phone` in E.164 format.\n\n**Video mode:** Creates a video room and emails the candidate a join link. Candidate clicks the link to start the AI interview. Pass `send_invite: false` to skip our email and send `join_url` yourself; `invite_sent` in the response says which happened.\n\nThe AI bot conducts a 15-30 minute structured screening based on the job description. After the call ends, the recording is automatically transcribed and analyzed. Poll `GET /interviews/{id}` or register a webhook for `interview.analyzed` to get results.\n\n**Repeat screenings.** A second screening of the same candidate on the same role is told every question the interviewer asked in that candidate's earlier completed interviews on the role, and does not re-ask them. The topics may overlap; the questions will not.","operationId":"trigger_screening_api_public_v1_screening_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/artifacts":{"post":{"tags":["Public API v1 — Artifacts","Public API v1 — Artifacts"],"summary":"Build a rubric + screening link from a job description","description":"Extracts a real competency rubric from the job description and mints a reusable candidate-screening link, exactly as the campaign console's scout does for a live req. Every artifact lands under BetterHire's house workspace (this is an outbound-campaign primitive, not a per-customer role), so an API key only needs write access — not an existing organization role or job board.","operationId":"create_artifact_api_public_v1_artifacts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtifactRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtifactResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/internal/oauth/client/{client_id}":{"get":{"tags":["Internal OAuth","Internal OAuth"],"summary":"Get Client For Consent","description":"Resolve a client so the consent page can name who is asking.\n\nThe consent page only ever sees a client_id in its query string, so the\nname and redirect must be resolved here, server-side, against the row we\nactually registered — anything carried in the URL is attacker-supplied.\n\n`trusted` is computed from the redirect HOST, never from client_name:\n/register is open (MCP spec) and stores whatever name the caller sends,\nso an attacker can register as \"Claude\". See OAUTH_CONFIG.","operationId":"get_client_for_consent_api_internal_oauth_client__client_id__get","parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/oauth/authorize":{"post":{"tags":["Internal OAuth","Internal OAuth"],"summary":"Authorize","description":"Authenticate a user with email/password and mint an OAuth authorization\ncode (the /oauth/authorize consent page's submit).\n\nReplaces the direct-DB Next.js route (API proxy sweep): password\nverification reuses core.auth.verify_password — the same helper and check\nchain as /api/auth/login — and the code INSERT is the store-code logic with\nredirect_uri_provided_explicitly=true. Protected by the internal key like\nstore-code; the user-facing credential check is the body itself.","operationId":"authorize_api_internal_oauth_authorize_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/oauth/store-code":{"post":{"tags":["Internal OAuth","Internal OAuth"],"summary":"Store Authorization Code","description":"Store an OAuth authorization code in the database.\n\nCalled by the frontend after user authenticates on /oauth/authorize page.\nProtected by internal key (JWT_SECRET_KEY).","operationId":"store_authorization_code_api_internal_oauth_store_code_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreCodeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/candidates/search":{"get":{"tags":["Public API v1 — Candidates","Public API v1 — Candidates"],"summary":"Search candidates","description":"Search for existing candidates by name or email. Returns matching candidates with interview history and scores. Use this before scheduling to check if a candidate already exists.","operationId":"search_candidates_api_public_v1_candidates_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"name","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"description":"Search by candidate name (partial, case-insensitive).","title":"Name"},"description":"Search by candidate name (partial, case-insensitive)."},{"name":"email","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":254},{"type":"null"}],"description":"Search by email (partial, case-insensitive).","title":"Email"},"description":"Search by email (partial, case-insensitive)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Max results.","default":10,"title":"Limit"},"description":"Max results."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandidateSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/match":{"post":{"tags":["Public API v1 — Resume Matching","Public API v1 — Resume Matching"],"summary":"Match resumes against a job description","description":"Match up to 10 resumes against a job description. Returns ranked candidates with fit scores, requirements checklist, strengths, concerns, and suggested Round 1 interview questions.\n\nResults are cached — same resume + JD won't re-run analysis.\n\nUse the returned match_id to screen or schedule top candidates.","operationId":"match_resumes_api_public_v1_match_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/interviews/schedule":{"post":{"tags":["Public API v1 — Scheduling","Public API v1 — Scheduling"],"summary":"Schedule a human interview with AI recording","description":"Schedule an interview between a human interviewer and candidate. TheBetterHire bot joins the Google Meet automatically, records the conversation, then transcribes and analyzes it. Results are available via GET /interviews/{id} after the meeting ends.\n\nIf Google Calendar is connected, a Meet link is created automatically and calendar invites are sent to both interviewer and candidate.\n\nIf no calendar connection, provide a `meeting_url` (Google Meet link) manually.","operationId":"schedule_interview_api_public_v1_interviews_schedule_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__public_scheduling__ScheduleInterviewRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__public_scheduling__ScheduleInterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/interviews/schedule/brain":{"post":{"tags":["Public API v1 — Scheduling","Public API v1 — Scheduling"],"summary":"Schedule an AI-led (brain) interview","description":"Schedule a structured interview where TheBetterHire's brain agent is the interviewer — no human required. The candidate receives an invitation email with a join link; at the scheduled time they click through and converse with the brain via voice. Results are available via GET /interviews/{id} after the session ends.\n\n**On demand:** omit `scheduled_at` and the join link is live the moment it is created — the candidate starts when they click. An on-demand interview takes the same lengths as a scheduled one.\n\nPass `send_invite: false` to skip our email and send `join_url` to the candidate yourself; `invite_sent` in the response says which happened.\n\n**Requirements:**\n- A job role with a competency rubric: create one with POST /job-roles, or pass the `job_role_id` of a role whose `interview_ready` is true on GET /job-roles/{id}.\n- Duration must be 45, 60, or 90 minutes; 30 is also accepted for a behavioural interview on an entry-level role.\n- Resume text is required so the brain can verify claims and tailor questions.\n\nIf the rubric doesn't fit the chosen duration, returns 400 with a recruiter-friendly explanation suggesting a longer slot or trimmed rubric.","operationId":"schedule_brain_interview_api_public_v1_interviews_schedule_brain_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrainScheduleInterviewRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrainScheduleInterviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/feedback":{"post":{"tags":["Public API v1 — Feedback","Public API v1 — Feedback"],"summary":"Submit feedback","description":"Record user feedback after a key action (screening, matching, etc.).","operationId":"submit_feedback_api_public_v1_feedback_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/interviews/{interview_id}/candidate-feedback":{"post":{"tags":["Public API v1 — Candidate Feedback","Public API v1 — Candidate Feedback"],"summary":"Generate candidate-facing feedback for a completed interview","description":"Turns the interview's scorecard into feedback written for the candidate: the one gap to the next level, the habit that cost the most (with a pointer to the moment), the best moment (with a pointer), one line per competency, and, when `self_read` is given, what the interviewer actually heard.\n\nFrom contract 2.0, `feedback.structure` lists the candidate's stories (a main question plus the interviewer's follow-ups): each framework part `present`, `when_asked` (given only after the interviewer asked) or `missing`, with `ref.first_turn` / `ref.last_turn` indexing `transcript.segments[]` on `GET /interviews/{id}`. `structure.status` is `ready`, `not_applicable` (skill rounds) or `unavailable` (the rest of the feedback still stands). From 2.1, `structure.openings` reads the introduction and the \"why this role\" answer: `seconds_spoken` (the candidate's own turns) and `focus` (`on_role`, `partly` or `wandered`), with a note.\n\nEvery pointer is `{competency, evidence}`: the competency `name` and an index into that competency's `evidence[]` on `GET /interviews/{id}`, so you can play the seconds behind it. No numbers ever appear in the text.\n\nWorks for every completed interview kind. Generated once and stored: a second call returns the same object without a charge unless `regenerate: true`. `status: unavailable` with `unavailable_reason` is a normal, unbilled answer (`not_scored` when the transcript-quality gate stripped scoring, `generation_failed` after three rejected attempts). 409 `FEEDBACK_IN_PROGRESS` with `Retry-After` while another request is generating feedback for the same interview.\n\nWith `\"async\": true`, 202 `{status: \"generating\"}` instead of waiting; the `candidate_feedback.completed` webhook (`data.status`, `data.structure_status`) fires when it is stored, and GET returns it.\n\nBilled once per generation as the `feedback` action, above the per-candidate cap.","operationId":"generate_candidate_feedback_api_public_v1_interviews__interview_id__candidate_feedback_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandidateFeedbackRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandidateFeedbackResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Public API v1 — Candidate Feedback","Public API v1 — Candidate Feedback"],"summary":"Get stored candidate feedback","description":"The feedback stored by the POST, unchanged. 404 `FEEDBACK_NOT_GENERATED` when the POST was never made, or when the interview has been re-analysed since (the stored pointers would no longer index the current scorecard). 409 `FEEDBACK_IN_PROGRESS` with `Retry-After` while a generation is running, including a regenerate with an older result stored: wait and GET again, don't POST again.","operationId":"get_candidate_feedback_api_public_v1_interviews__interview_id__candidate_feedback_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandidateFeedbackResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/downloads/create-token":{"post":{"tags":["Public API v1 — Downloads","Public API v1 — Downloads"],"summary":"Create a signed download URL","description":"Generates a temporary token-based URL for downloading PDFs. The URL works without authentication (token = auth). Expires in 10 minutes.","operationId":"create_download_token_api_public_v1_downloads_create_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTokenRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/downloads/{token}":{"get":{"tags":["Public API v1 — Downloads","Public API v1 — Downloads"],"summary":"Download file by token (no auth required)","operationId":"download_by_token_api_public_v1_downloads__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/uploads/create-session":{"post":{"tags":["Public API v1 — File Uploads","Public API v1 — File Uploads"],"summary":"Create file upload session","description":"Creates a temporary upload session with a unique URL. Share this URL with the user to upload JD and resume files. Session expires in 1 hour.","operationId":"create_session_api_public_v1_uploads_create_session_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/uploads/{token}/jd":{"post":{"tags":["Public API v1 — File Uploads","Public API v1 — File Uploads"],"summary":"Upload job description file","description":"Upload a single JD file (PDF, DOCX, or TXT, max 5MB). Text is extracted automatically.","operationId":"upload_jd_api_public_v1_uploads__token__jd_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_jd_api_public_v1_uploads__token__jd_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/uploads/{token}/resumes":{"post":{"tags":["Public API v1 — File Uploads","Public API v1 — File Uploads"],"summary":"Upload resume files","description":"Upload up to 10 resume files (PDF, DOCX, or TXT, max 5MB each). Text is extracted automatically from each.","operationId":"upload_resumes_api_public_v1_uploads__token__resumes_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_resumes_api_public_v1_uploads__token__resumes_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/uploads/{token}/status":{"get":{"tags":["Public API v1 — File Uploads","Public API v1 — File Uploads"],"summary":"Check upload session status","operationId":"get_upload_status_api_public_v1_uploads__token__status_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/uploads/pending":{"get":{"tags":["Public API v1 — File Uploads","Public API v1 — File Uploads"],"summary":"Get pending/ready uploads with extracted text","description":"Returns all ready upload sessions for this organization. Used by MCP tools to retrieve extracted JD and resume text after user uploads.","operationId":"get_pending_uploads_api_public_v1_uploads_pending_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/uploads/{token}/data":{"get":{"tags":["Public API v1 — File Uploads","Public API v1 — File Uploads"],"summary":"Get full upload data","description":"Returns the extracted JD text and resume texts for an upload session, whatever its status: a resume-only session (no JD yet) is served too. The token is the capability; the session must belong to your organization and not have expired. Used by match_resumes server-side, and by integrators that upload resumes and want the text back (platform request 0003 §3).","operationId":"get_upload_data_api_public_v1_uploads__token__data_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/job-roles":{"post":{"tags":["Public API v1 — Job Roles","Public API v1 — Job Roles"],"summary":"Create a job role with a competency rubric","description":"Create the role an interview is scored against. Omit `competencies` and BetterHire extracts 5–7 from the job description — the same extraction the dashboard runs. Supply them to use your own rubric.\n\nThe response says whether an AI-led interview can be scheduled on the role (`interview_ready`) and the shortest slot it fits, so a rubric that would be rejected at scheduling time is caught here.\n\n`title` may be omitted: the extraction reads the title, level and company off the posting and returns them as `inferred` (validated; null when the text does not say). One role per title: a second role with the same title would make every `role`-by-title call on this API ambiguous, so that returns 409 with the existing role's id.\n\nRate limit: 50 requests/hour per API key. Supports `Idempotency-Key`.","operationId":"create_job_role_api_public_v1_job_roles_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__public_job_roles__JobRoleCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__public_job_roles__JobRoleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Public API v1 — Job Roles","Public API v1 — Job Roles"],"summary":"List job roles","description":"Active job roles in your organization, newest first. `competency_count` of 0 means the role has no rubric and cannot run an AI-led interview.","operationId":"list_job_roles_api_public_v1_job_roles_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"title","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"description":"Case-insensitive substring match.","title":"Title"},"description":"Case-insensitive substring match."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Number of results per page.","default":20,"title":"Limit"},"description":"Number of results per page."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Offset for pagination.","default":0,"title":"Offset"},"description":"Offset for pagination."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__public_job_roles__JobRoleListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/job-roles/{job_role_id}":{"get":{"tags":["Public API v1 — Job Roles","Public API v1 — Job Roles"],"summary":"Get a job role","description":"The role's rubric and whether an AI-led interview can be scheduled on it.","operationId":"get_job_role_api_public_v1_job_roles__job_role_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_role_id","in":"path","required":true,"schema":{"type":"string","title":"Job Role Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/backend__routers__public_job_roles__JobRoleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/skills":{"get":{"tags":["Public API v1 — Skills","Public API v1 — Skills"],"summary":"List skill assessments","description":"Every skill assessment a brain interview can run as a skill round. Pass a `skill_id` to POST /interviews/schedule/brain. The catalog is global, not per organization, and identical for sandbox keys.\n\nRate limit: 200 requests/hour per API key.","operationId":"list_skills_api_public_v1_skills_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkillListResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/i/{token}":{"get":{"tags":["campaign"],"summary":"Campaign Redirect","description":"Log the click, 302 to the FOUNDER PREVIEW page (rubric + sample\nscorecard + candidate link) — NOT the raw interview join, which would\ninterview the founder as if they were the candidate. Unknown token ->\nthe marketing site rather than a bare 404 — a mistyped link from a\ncurious prospect should never dead-end.","operationId":"campaign_redirect_i__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/campaign-preview/{token}":{"get":{"tags":["campaign"],"summary":"Campaign Preview","description":"Founder preview data for /preview/{token} (public — the opaque token\nis the capability): company, role, the rubric we built, and the\ncandidate screening link to share.","operationId":"campaign_preview_api_public_campaign_preview__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/campaign-preview/{token}/report.pdf":{"get":{"tags":["campaign"],"summary":"Campaign Preview Report","description":"The founder preview's sample report, as the PDF a founder forwards.\n\nThis is the artifact that travels: the page is read once by one person, but\nthe PDF gets sent into a hiring channel and read by the people who actually\ndecide. Serving the REAL report generator rather than a mock is the point —\nwhat they forward is exactly what their own candidates would produce.\n\nSECURITY — why a public PDF route is safe here. The caller never names an\ninterview; the opaque token names a target, and the only interview\nreachable is that target's `hero_interview_id`, which is always a synthetic\ncandidate in the house org. There is no parameter to tamper with that could\nreach a customer's interview, so this cannot become an enumeration hole.","operationId":"campaign_preview_report_api_public_campaign_preview__token__report_pdf_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/campaign-preview/{token}/audio":{"get":{"tags":["campaign"],"summary":"Campaign Preview Audio","description":"The sample recording, behind a STABLE url.\n\nRedirects to a freshly-minted presigned S3 url on every request.\n`interviews.audio_url` could not be used directly: it is presigned with a\n6-hour expiry, and a cold-outreach page lives for weeks — every prospect\nopening the link after the first afternoon would find a silently dead\n<audio> element.\n\nPublic for the same reason as the report route: the caller names a token,\nnever an object, so the only recording reachable is that target's own\nsynthetic house-org sample.","operationId":"campaign_preview_audio_api_public_campaign_preview__token__audio_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/campaign-claim/{token}":{"post":{"tags":["campaign"],"summary":"Campaign Claim","description":"Founder-preview 'claim your workspace': provision the org, hand over\nevery req we built for them, and mail a magic link to the address on\nrecord. Public — the opaque token is the capability, exactly like the\ninvite endpoint — but note the link is NEVER returned in this response;\nit only ever goes to the mailbox, which is what makes a forwarded preview\nURL useless to a stranger.","operationId":"campaign_claim_api_public_campaign_claim__token__post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/CampaignClaimBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/campaign-invite/{token}":{"post":{"tags":["campaign"],"summary":"Campaign Invite","description":"Founder-preview 'invite your candidates': send each entered email a\nbranded Join Call invite (public — the opaque token is the capability).\nDeduped + hard-capped at max_invites_per_token so a leaked link can't\nblast. Returns {sent, skipped, invited_total, capped}.","operationId":"campaign_invite_api_public_campaign_invite__token__post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignInviteBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/campaign-screen/{token}":{"get":{"tags":["campaign"],"summary":"Campaign Screen Info","description":"Branding for the candidate-facing /screen/{token} page (public, no\nauth — the opaque token is the capability). 404 for unknown/killed.","operationId":"campaign_screen_info_api_public_campaign_screen__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["campaign"],"summary":"Campaign Screen Start","description":"A candidate applies via the forwarded link -> spawn THEIR own interview\nand return their private join_url. One link, one fresh interview per\ncandidate (reuses the per-candidate spawn). Public by design.\n\nIdentity only. The resume is collected on the interview lobby, which is\nwhere BOTH candidate paths (this link and the emailed invite) converge —\nasking here as well made a /screen candidate submit it twice, and the\nfirst copy was the weaker one (raw text, never parsed onto the candidate\nrecord). One step, one implementation, honouring candidate_resume_mode.","operationId":"campaign_screen_start_api_public_campaign_screen__token__post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenStartBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/playbook":{"get":{"tags":["campaign"],"summary":"Get Playbook","description":"Serve the living GTM playbook markdown (docs/gtm/gtm-playbook.md) so\nthe console can render it. The file IS the single source of truth —\nedit it, commit, deploy, console updates.","operationId":"get_playbook_api_campaign_playbook_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/campaign/run":{"post":{"tags":["campaign"],"summary":"Start Run","description":"Kick off a scout+build in the background (the console 'Run scout'\nbutton). Returns immediately; the UI polls /run/status. 409 if a run is\nalready in flight — one at a time.","operationId":"start_run_api_campaign_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/campaign/run/status":{"get":{"tags":["campaign"],"summary":"Run Status","description":"Current state of the console-triggered scout (drives the button).","operationId":"run_status_api_campaign_run_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/campaign/send-now":{"post":{"tags":["campaign"],"summary":"Send Now","description":"Console 'Send queued now': run the due-touch send loop in-process on\nthe BE (where the email service routes to Mailpit on staging / the real\nprovider in prod) — on demand, without waiting for the daily cron.\nReturns {due, sent, failed}. Fails CLOSED without a catcher, so this can\nnever leak real mail from staging.","operationId":"send_now_api_campaign_send_now_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/campaign/targets":{"get":{"tags":["campaign"],"summary":"List Targets","operationId":"list_targets_api_campaign_targets_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"stage","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/accounts":{"get":{"tags":["campaign"],"summary":"List Accounts","description":"Account-lane targets with their role counts.","operationId":"list_accounts_api_campaign_accounts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["campaign"],"summary":"Create Account","description":"Top-down lane: instrument a company's board as ONE target carrying many\nroles. One decision-maker, one stage, one touch sequence — however many\nroles we build (see migration 126 for why the row-per-role shape was\nrejected).","operationId":"create_account_api_campaign_accounts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/campaign/accounts/{target_id}/hero":{"post":{"tags":["campaign"],"summary":"Regenerate Hero","description":"(Re)generate an account's sample interview.\n\nThe recovery path the deferred-hero design was missing. A hero is not\nguaranteed on the first try — the criteria deliberately reject a candidate\nwith no visible weakness, because a uniformly-strong sample shows the\nproduct discriminating nothing — and generation can also lose its worker\nmid-flight. Either way the account sat at \"generating…\" with nothing to\npress.","operationId":"regenerate_hero_api_campaign_accounts__target_id__hero_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/accounts/board":{"get":{"tags":["campaign"],"summary":"Preview Board","description":"Read a board WITHOUT instrumenting it: what's live, what the gate keeps,\nand each req's url so the operator can nominate roles (pick_req_urls).\n\nExists because choosing the lead is only possible if you can see the board\nfirst. Read-only and cheap — one adapter fetch, no rubric spend, no writes —\nso it is safe to call repeatedly while deciding.","operationId":"preview_board_api_campaign_accounts_board_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"adapter","in":"query","required":true,"schema":{"type":"string","title":"Adapter"}},{"name":"seed","in":"query","required":true,"schema":{"type":"string","title":"Seed"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/manual":{"post":{"tags":["campaign"],"summary":"Create Manual Target","description":"Console 'Create' tab: paste a JD (pulled from LinkedIn etc.) -> build\nthe artifact. Reuses the scout's factory, so the row lands at\nneeds_review like any other — the review + send gates are unchanged.","operationId":"create_manual_target_api_campaign_manual_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManualTargetBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/campaign/targets/{target_id}/contact":{"post":{"tags":["campaign"],"summary":"Set Target Contact","description":"Console contact top-up: name/email found during review (LinkedIn,\nteam page) + the human's verified assertion the Gmail sender trusts.","operationId":"set_target_contact_api_campaign_targets__target_id__contact_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/targets/{target_id}/contacts":{"get":{"tags":["campaign"],"summary":"List Contacts","description":"Every decision-maker on one target, primary first.","operationId":"list_contacts_api_campaign_targets__target_id__contacts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["campaign"],"summary":"Add Contact","description":"Add another decision-maker at this company — founder, VP, head of\nrecruiting. They share the target's one artifact, preview link and claim;\nonly the email thread is theirs. The stagger behind the people already\nhere is applied by the service, not chosen here.","operationId":"add_contact_api_campaign_targets__target_id__contacts_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TargetContactBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/targets/{target_id}/mark-sent":{"post":{"tags":["campaign"],"summary":"Mark Target Sent","description":"Console manual-Gmail workflow: the operator pasted touch N into Gmail\nand sent it to the PRIMARY contact.\n\nAdvances both rows. Moving only the target would leave that person's\ncontact row at 'queued' while the company sits in the in-play set, and\nsendable_contacts would then hand the automated sender the same touch 1\nfor the same person — a duplicate cold email to someone we just mailed.","operationId":"mark_target_sent_api_campaign_targets__target_id__mark_sent_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkSentBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/contacts/{contact_id}/verify-email":{"post":{"tags":["campaign"],"summary":"Verify Contact Email Endpoint","description":"Verify ONE decision-maker's address.\n\nThe sender selects on the contact row, so this is the only way a person\nadded after the primary can ever become sendable — verifying the target\nratifies the primary and nobody else.","operationId":"verify_contact_email_endpoint_api_campaign_contacts__contact_id__verify_email_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/contacts/{contact_id}/trust":{"post":{"tags":["campaign"],"summary":"Trust Contact Email","description":"Human assertion that ONE contact's address is good.\n\nThe escape hatch for a catch-all domain. A catch-all accepts mail to any\naddress, so an SMTP probe can never confirm a mailbox there — the verdict\nis 'unverifiable', NOT 'invalid'. Without this, every contact at such a\nfirm was permanently unsendable however right the address was, and a good\nshare of agency domains are catch-all.\n\nRecorded as method='human' so the provenance stays honest: the sender is\ntrusting a person, not a probe.","operationId":"trust_contact_email_api_campaign_contacts__contact_id__trust_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrustContactBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/targets/{target_id}/verify-email":{"post":{"tags":["campaign"],"summary":"Verify Target Email Endpoint","description":"Establish trust in the address a target already holds — free\ncross-check first, paid mailbox probe only where nothing corroborates.\nSee runner.verify_target_email for the tier rules.","operationId":"verify_target_email_endpoint_api_campaign_targets__target_id__verify_email_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/targets/{target_id}/posting-url":{"post":{"tags":["campaign"],"summary":"Set Target Posting Url","description":"Fix the console's readable 'posting' link for one target. Display\nonly — req_url stays the dedup identity (see migration 117).","operationId":"set_target_posting_url_api_campaign_targets__target_id__posting_url_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostingUrlBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/verify-emails":{"post":{"tags":["campaign"],"summary":"Verify All Emails","description":"Console 'Verify all': sweep every target holding an unverified address.\nReturns {checked, verified, by_verdict}.","operationId":"verify_all_emails_api_campaign_verify_emails_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/campaign/reenrich":{"post":{"tags":["campaign"],"summary":"Reenrich Missing Emails","description":"Console 'Re-enrich': retry email discovery for live targets with no\naddress (enrichment otherwise only runs at creation). Returns\n{checked, verified, filled, still_empty}.","operationId":"reenrich_missing_emails_api_campaign_reenrich_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/campaign/targets/{target_id}/template":{"post":{"tags":["campaign"],"summary":"Set Target Template","description":"Set which email angle a target uses. Rejects an angle that isn't\noffered for the target's lane (submittal is agency-only).","operationId":"set_target_template_api_campaign_targets__target_id__template_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/preview/{target_id}":{"get":{"tags":["campaign"],"summary":"Preview Target","description":"Render all four touches for a target under a given angle (defaults to\nits current one) — the console's live template preview.","operationId":"preview_target_api_campaign_preview__target_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}},{"name":"template","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/targets/{target_id}/stage":{"post":{"tags":["campaign"],"summary":"Set Target Stage","operationId":"set_target_stage_api_campaign_targets__target_id__stage_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StageBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/sender/queue":{"get":{"tags":["campaign"],"summary":"Sender Queue","description":"Due sends for the external sender: target + fully rendered touch.\nRendering server-side keeps copy versioned in the repo — the sender is\na dumb pipe that mails what it's told and reports back.","operationId":"sender_queue_api_campaign_sender_queue_get","parameters":[{"name":"x-campaign-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Campaign-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/sender/targets/{target_id}/sent":{"post":{"tags":["campaign"],"summary":"Sender Mark Sent","operationId":"sender_mark_sent_api_campaign_sender_targets__target_id__sent_post","parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}},{"name":"x-campaign-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Campaign-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/sender/targets/{target_id}/outcome":{"post":{"tags":["campaign"],"summary":"Sender Mark Outcome","operationId":"sender_mark_outcome_api_campaign_sender_targets__target_id__outcome_post","parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"string","title":"Target Id"}},{"name":"x-campaign-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Campaign-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutcomeBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/sender/contacts/{contact_id}/sent":{"post":{"tags":["campaign"],"summary":"Sender Mark Contact Sent","description":"Report a touch sent to ONE person. The queue now yields contacts, so\nthis is the id to report against; the company row is mirrored so it leaves\n'queued' and the console still shows progress.","operationId":"sender_mark_contact_sent_api_campaign_sender_contacts__contact_id__sent_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"x-campaign-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Campaign-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/campaign/sender/contacts/{contact_id}/outcome":{"post":{"tags":["campaign"],"summary":"Sender Mark Contact Outcome","description":"A reply or bounce from ONE person. Recorded against them AND against the\ncompany — moving the target out of the in-play set is what stops every\nother colleague mid-sequence.","operationId":"sender_mark_contact_outcome_api_campaign_sender_contacts__contact_id__outcome_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"x-campaign-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Campaign-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutcomeBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/reconcile-recordings":{"post":{"tags":["Internal — Staff Tools","internal"],"summary":"Reconcile Recordings","description":"Backfill Daily recordings that finished but never mirrored to S3 (ADR-0034\nP3.11). With `interview_id`, mirror that one interview now; without it, sweep\nrecent completed pipecat interviews. The on-demand twin of the boot-time sweep —\nuse it to recover a recording the webhook + /end poll both missed (e.g. after\ndeploy churn). Internal-staff only (404 otherwise). Idempotent — a no-op when\nthe recording is already mirrored or not finished at Daily yet.","operationId":"reconcile_recordings_api_internal_reconcile_recordings_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interview_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"max_age_hours","in":"query","required":false,"schema":{"type":"integer","default":72,"title":"Max Age Hours"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/reconciliation":{"get":{"tags":["Internal — Staff Tools","internal"],"summary":"Billing Reconciliation","description":"Completed-but-unbilled interviews — the silent billing-failure detector.\n\nLists chargeable interviews that reached status='completed' but have no\ndeduction row, i.e. billing never fired. A capped action still writes a\n$0 audit row, so a *missing* row is a real miss, not a free action.\nInternal-staff only (404 to everyone else, same as the rest of this\nrouter). Read-only — no mutation, no backfill from here.","operationId":"billing_reconciliation_api_internal_reconciliation_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"window_days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Window Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/plan-quality":{"get":{"tags":["Internal — Staff Tools","internal"],"summary":"Plan Quality","description":"Brain interview-plan provenance — how many candidates got the\ndeterministic (un-personalised) fallback plan instead of a real LLM plan,\nand why. Reads the `plan_source`/`fallback_reason` flag the brain stamps\ninto `interviews.plan_json` (PR #101). Internal-staff only (404 otherwise).\nRead-only. Tier 1 only — join-perf tiles need separate instrumentation.","operationId":"plan_quality_api_internal_plan_quality_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"window_days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Window Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/feedback":{"get":{"tags":["Internal — Staff Tools","internal"],"summary":"Internal Feedback","description":"Dashboard Support submissions (question / bug / feature / suggest_skill),\nnewest first, across all orgs. Internal-staff only (404 otherwise).\nRead-only. `type` filters to a single kind.","operationId":"internal_feedback_api_internal_feedback_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/skills":{"get":{"tags":["Internal — Staff Tools","internal"],"summary":"Internal Skills","description":"Skill catalog — every specialist skill in the system with its full rubric,\nstages, curveballs/hints, and library signature. Doubles as the review surface\nfor expert rubric sign-off (1b). Internal-staff only (404 otherwise). Read-only;\nreads the in-process skill registry (no DB).","operationId":"internal_skills_api_internal_skills_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/internal/cap-giveaway":{"get":{"tags":["Internal — Staff Tools","internal"],"summary":"Cap Giveaway","description":"Cap giveaway — actions that billed LESS than unit price because the\ncandidate hit the per-candidate cap.\n\nThe OPPOSITE of /reconciliation: there billing never fired (a bug); here\nit fired correctly and charged less by design. Surfaces the revenue\nforgone to the cap promise (\"is the cap a black hole\" with real numbers).\nInternal-staff only (404 otherwise). Read-only.","operationId":"cap_giveaway_api_internal_cap_giveaway_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"window_days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Window Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/volume-leads":{"get":{"tags":["Internal — Staff Tools","internal"],"summary":"Volume Leads","description":"Volume / prepaid-pack interest from the billing 'Talk to us' overlay.\n\nEach is a support request (type='volume') stored in user_feedback\n(context='support:volume') plus an inbox email. This lists them in-app so\nstaff don't dig through the inbox. Internal-staff only (404). Read-only.","operationId":"volume_leads_api_internal_volume_leads_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"window_days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Window Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/config-registry":{"get":{"tags":["Internal — Staff Tools","internal"],"summary":"Config Registry","description":"Aggregate typed configs + env vars across the BE and brain services.","operationId":"config_registry_api_internal_config_registry_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/internal/shadow-eval/run":{"post":{"tags":["Internal — Shadow Eval","internal"],"summary":"Run Eval","description":"Kick off a shadow-scoring eval as a background asyncio task.\n\nReturns immediately with the job_id; eval continues running even if\nthe client disconnects (the user's flaky wifi was the whole reason\nwe built this endpoint).\n\nQuery params:\n  n: transcripts per persona (default 10 → 40 total)\n  llm: \"anthropic\" or \"deepseek\" — generator model","operationId":"run_eval_api_internal_shadow_eval_run_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"n","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"N"}},{"name":"llm","in":"query","required":false,"schema":{"type":"string","default":"anthropic","title":"Llm"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/shadow-eval/status/{job_id}":{"get":{"tags":["Internal — Shadow Eval","internal"],"summary":"Get Status","description":"Poll job status. Returns 404 if job_id is unknown (was the BE\nredeployed since the job was created? In-memory state is gone).\nResults on disk may still exist — check results_url too.","operationId":"get_status_api_internal_shadow_eval_status__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/shadow-eval/results/{job_id}":{"get":{"tags":["Internal — Shadow Eval","internal"],"summary":"Get Results","description":"Return the analysis JSON (per-persona distributions + moat verdict).\n404 if results don't exist yet.","operationId":"get_results_api_internal_shadow_eval_results__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/shadow-eval/results/{job_id}/scores.csv":{"get":{"tags":["Internal — Shadow Eval","internal"],"summary":"Get Scores Csv","description":"Return the raw scored-rows CSV — one row per transcript with both\nLLM-judge and brain scores. Same auth/404 as the analysis endpoint.","operationId":"get_scores_csv_api_internal_shadow_eval_results__job_id__scores_csv_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/shadow-eval/list":{"get":{"tags":["Internal — Shadow Eval","internal"],"summary":"List Jobs","description":"List all in-memory jobs + any result directories on disk that\nhave no live job entry (typically jobs that survived a BE redeploy).","operationId":"list_jobs_api_internal_shadow_eval_list_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/":{"get":{"tags":["Health"],"summary":"Root","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"tags":["Health"],"summary":"Health","description":"Health check endpoint - designed to always return 200 for Railway healthchecks","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/debug/routers":{"get":{"tags":["Health"],"summary":"Debug Routers","description":"Debug endpoint to show which routers loaded successfully","operationId":"debug_routers_debug_routers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/debug/verify-code":{"get":{"tags":["Health"],"summary":"Verify Code Version","description":"Verify that the latest code is loaded - check critical class methods\nUseful for confirming code changes after server reload","operationId":"verify_code_version_debug_verify_code_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/debug/scheduler":{"get":{"tags":["Health"],"summary":"Debug Scheduler","description":"Debug endpoint to check scheduler and bot status","operationId":"debug_scheduler_debug_scheduler_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/interviews":{"get":{"tags":["Interviews"],"summary":"List Interviews","description":"List interviews for the current tenant\n\n- **limit**: Maximum number of interviews to return (default: 10)\n- **status**: Filter by status (pending, processing, completed, failed)","operationId":"list_interviews_api_v1_interviews_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Limit"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/interviews/{interview_id}/upload-url":{"get":{"tags":["Interviews"],"summary":"Get Presigned Upload URL","description":"Get presigned URL for direct client upload to cloud storage","operationId":"get_upload_url_api_v1_interviews__interview_id__upload_url_get","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}},{"name":"filename","in":"query","required":true,"schema":{"type":"string","title":"Filename"}},{"name":"content_type","in":"query","required":false,"schema":{"type":"string","default":"audio/mpeg","title":"Content Type"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/interviews/{interview_id}/upload":{"post":{"tags":["Interviews"],"summary":"Upload Audio","description":"Upload audio file and start processing","operationId":"upload_audio_api_v1_interviews__interview_id__upload_post","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_audio_api_v1_interviews__interview_id__upload_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/interviews/{interview_id}":{"get":{"tags":["Interviews"],"summary":"Get Interview","description":"Get interview status and results","operationId":"get_interview_api_v1_interviews__interview_id__get","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/interviews/{interview_id}/export":{"get":{"tags":["Interviews"],"summary":"Export Interview Report","description":"Export interview report as HTML (can be printed to PDF)","operationId":"export_interview_report_api_v1_interviews__interview_id__export_get","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","title":"Interview Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AccountBody":{"properties":{"company_name":{"type":"string","title":"Company Name"},"adapter":{"type":"string","title":"Adapter"},"seed":{"type":"string","title":"Seed"},"company_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Domain"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"email_verified":{"type":"boolean","title":"Email Verified","default":false},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","default":"US"},"cap":{"type":"integer","title":"Cap","default":10},"lane":{"type":"string","title":"Lane","default":"agency"},"pick_req_urls":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Pick Req Urls"}},"type":"object","required":["company_name","adapter","seed"],"title":"AccountBody"},"ActionPlanItem":{"properties":{"priority":{"type":"string","title":"Priority","description":"'critical', 'high', or 'medium'.","examples":["critical"]},"title":{"type":"string","title":"Title","description":"Action title.","examples":["Address Technical Roadmap Execution Gap"]},"action":{"type":"string","title":"Action","description":"Detailed action description."},"deadline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deadline","description":"Suggested deadline.","examples":["Within 48 hours"]}},"type":"object","required":["priority","title","action"],"title":"ActionPlanItem","description":"A prioritized next step for the hiring process."},"AgencyInvite":{"properties":{"agency_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agency Email"},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"AgencyInvite"},"AggregatedItem":{"properties":{"text":{"type":"string","title":"Text","description":"Strength or concern text."},"frequency":{"type":"string","title":"Frequency","description":"How many rounds mentioned this.","examples":["3/4"]},"rounds":{"items":{"type":"string"},"type":"array","title":"Rounds","description":"Which rounds mentioned this."}},"type":"object","required":["text","frequency"],"title":"AggregatedItem"},"AnalysisRequest":{"properties":{"report_type":{"type":"string","title":"Report Type","default":"recruiter"}},"type":"object","title":"AnalysisRequest","description":"Request to trigger analysis with specific report type"},"ApiKeyCreateRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":3,"title":"Name","description":"Human label so a key can be identified later without revealing it, e.g. 'ATS integration' or 'Zapier'."},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description"},"read_only":{"type":"boolean","title":"Read Only","description":"Read-only keys are rejected on every write route (schedule, submit, trigger, upload, match, webhook changes).","default":false},"sandbox":{"type":"boolean","title":"Sandbox","description":"Sandbox keys are free and isolated: they authenticate to a separate sandbox organisation, are never billed, and never reach a vendor. Use them to build an integration without paying for every mistake. They cannot see live data.","default":false}},"type":"object","required":["name"],"title":"ApiKeyCreateRequest"},"ArtifactRequest":{"properties":{"company_name":{"type":"string","minLength":1,"title":"Company Name","description":"The hiring company's display name."},"role_title":{"type":"string","minLength":1,"title":"Role Title","description":"The role's title."},"job_description":{"type":"string","title":"Job Description","description":"Full job description text. Needs enough real content for a competency rubric — a one-line summary will be rejected."},"lane":{"type":"string","enum":["agency","company"],"title":"Lane","description":"'agency' for a staffing/recruiting firm's own req, 'company' for a direct employer's req.","default":"company"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name","description":"Recruiter/decision-maker name, if known."},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Recruiter/decision-maker email, if known."},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","default":"US"},"req_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Req Url","description":"Source posting URL — used for dedup, never displayed raw."},"company_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Domain"}},"type":"object","required":["company_name","role_title","job_description"],"title":"ArtifactRequest","description":"A job posting to turn into a rubric + candidate screening link."},"ArtifactResponse":{"properties":{"job_role_id":{"type":"string","title":"Job Role Id"},"rubric_summary":{"items":{"$ref":"#/components/schemas/RubricCompetency"},"type":"array","title":"Rubric Summary"},"preview_url":{"type":"string","title":"Preview Url","description":"The tracked /i/{token} founder-preview page — rubric plus the forwardable candidate link, this is what an outreach email should carry."},"screening_link":{"type":"string","title":"Screening Link","description":"The raw /screen/{token} candidate entry link — one link, one fresh AI interview per candidate who clicks it."}},"type":"object","required":["job_role_id","rubric_summary","preview_url","screening_link"],"title":"ArtifactResponse"},"AssessmentPlan":{"properties":{"rounds":{"items":{"$ref":"#/components/schemas/AssessmentRound"},"type":"array","title":"Rounds","default":[]}},"type":"object","title":"AssessmentPlan","description":"Ordered assessment rounds for a role. Empty `rounds` on update means\n\"clear the plan\" (stored as NULL, the pre-plan sentinel — mig 097)."},"AssessmentRound":{"properties":{"round_id":{"type":"string","title":"Round Id"},"kind":{"type":"string","title":"Kind"},"skill_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skill Id"},"label":{"type":"string","title":"Label"}},"type":"object","required":["round_id","kind","label"],"title":"AssessmentRound","description":"One round in a role's ordered assessment plan.\n\nDescriptive, not enforced — see docs/design/role-assessment-plan.md.\n`round_id` is a stable client-generated slug so an interview's\n`metadata.plan_round` snapshot survives plan reordering/renaming."},"AudioUrlResponse":{"properties":{"url":{"type":"string","title":"Url","description":"Time-limited URL to the interview recording. Open in any browser or audio player. Use with evidence timestamps (start_seconds, end_seconds) from the scorecard to seek to specific moments.","examples":["https://s3.amazonaws.com/bucket/key?X-Amz-Signature=..."]},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the URL expires.","examples":[3600]},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds","description":"Total recording duration in seconds, if known.","examples":[2340]},"clip":{"anyOf":[{"$ref":"#/components/schemas/ClipWindow"},{"type":"null"}],"description":"Set when `start`/`end` were given: the URL is an audio-only clip of that window (same one-hour life), and duration_seconds is its length."}},"type":"object","required":["url","expires_in"],"title":"AudioUrlResponse","description":"Playable audio URL for an interview recording."},"AuthResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"user":{"type":"object","title":"User"},"organization":{"type":"object","title":"Organization"},"joined_existing_org":{"type":"boolean","title":"Joined Existing Org","default":false}},"type":"object","required":["access_token","refresh_token","user","organization"],"title":"AuthResponse","description":"Successful authentication response"},"AuthorizeRequest":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"client_id":{"type":"string","title":"Client Id"},"redirect_uri":{"type":"string","title":"Redirect Uri"},"code_challenge":{"type":"string","title":"Code Challenge"},"scope":{"type":"string","title":"Scope","default":"mcp:read mcp:write"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},"type":"object","required":["email","password","client_id","redirect_uri","code_challenge"],"title":"AuthorizeRequest"},"BalanceResponse":{"properties":{"balance_cents":{"type":"integer","title":"Balance Cents"},"balance_display":{"type":"string","title":"Balance Display"},"cap_cents":{"type":"integer","title":"Cap Cents"},"action_prices_cents":{"additionalProperties":{"type":"integer"},"type":"object","title":"Action Prices Cents"},"topup_packs_cents":{"items":{"type":"integer"},"type":"array","title":"Topup Packs Cents"},"min_topup_cents":{"type":"integer","title":"Min Topup Cents"},"lifetime_purchased_cents":{"type":"integer","title":"Lifetime Purchased Cents"},"lifetime_granted_cents":{"type":"integer","title":"Lifetime Granted Cents"},"lifetime_spent_cents":{"type":"integer","title":"Lifetime Spent Cents"},"reserved_cents":{"type":"integer","title":"Reserved Cents","default":0},"available_cents":{"type":"integer","title":"Available Cents","default":0}},"type":"object","required":["balance_cents","balance_display","cap_cents","action_prices_cents","topup_packs_cents","min_topup_cents","lifetime_purchased_cents","lifetime_granted_cents","lifetime_spent_cents"],"title":"BalanceResponse","description":"Wallet balance + this org's pricing context.\n\nUsed by GET /api/billing/balance (dashboard) and\nGET /api/public/v1/balance (Public API/MCP)."},"BankEntryResponse":{"properties":{"id":{"type":"string","title":"Id"},"candidate_id":{"type":"string","title":"Candidate Id"},"job_role_id":{"type":"string","title":"Job Role Id"},"question":{"type":"string","title":"Question"},"competency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Competency"},"rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rationale"},"priority":{"type":"string","title":"Priority"},"quality_grade":{"type":"number","title":"Quality Grade"},"source_type":{"type":"string","title":"Source Type"},"source_interview_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Interview Id"},"status":{"type":"string","title":"Status"},"asked_in_interview_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asked In Interview Id"},"asked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asked At"},"answered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answered At"},"answer_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer Summary"},"pending_review":{"type":"boolean","title":"Pending Review","default":false},"partial_answer_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partial Answer Summary"},"marked_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Marked By"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","candidate_id","job_role_id","question","priority","quality_grade","source_type","status","created_at","updated_at"],"title":"BankEntryResponse","description":"Wire shape for a single bank entry. Mirrors core.question_bank.BankEntry."},"BankListResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/BankEntryResponse"},"type":"array","title":"Entries"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["entries","count"],"title":"BankListResponse"},"BestMoment":{"properties":{"title":{"type":"string","maxLength":120,"minLength":8,"title":"Title"},"ref":{"$ref":"#/components/schemas/EvidenceRef"}},"type":"object","required":["title","ref"],"title":"BestMoment"},"Body_add_candidate_to_job_role_api_job_roles__job_role_id__candidates_post":{"properties":{"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"resume_file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Resume File"}},"type":"object","title":"Body_add_candidate_to_job_role_api_job_roles__job_role_id__candidates_post"},"Body_extract_candidate_from_resume_api_interviews_extract_from_resume_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_extract_candidate_from_resume_api_interviews_extract_from_resume_post"},"Body_extract_competencies_from_uploaded_file_api_job_roles_extract_competencies_from_file_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"},"extract_jd_text":{"type":"boolean","title":"Extract Jd Text","default":false}},"type":"object","required":["file"],"title":"Body_extract_competencies_from_uploaded_file_api_job_roles_extract_competencies_from_file_post"},"Body_extract_jd_text_only_api_job_roles_extract_jd_text_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_extract_jd_text_only_api_job_roles_extract_jd_text_post"},"Body_match_preview_api_job_roles_match_preview_post":{"properties":{"resume_file":{"type":"string","format":"binary","title":"Resume File"},"job_role_ids":{"type":"string","title":"Job Role Ids"}},"type":"object","required":["resume_file","job_role_ids"],"title":"Body_match_preview_api_job_roles_match_preview_post"},"Body_parse_resume_file_api_candidates_parse_resume_file_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_parse_resume_file_api_candidates_parse_resume_file_post"},"Body_upload_audio_api_v1_interviews__interview_id__upload_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_audio_api_v1_interviews__interview_id__upload_post"},"Body_upload_audio_for_test_api_interviews__interview_id__upload_audio_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_audio_for_test_api_interviews__interview_id__upload_audio_post"},"Body_upload_campaign_resume_api_interview_join__token__resume_post":{"properties":{"file":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"File"},"resume_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Text"}},"type":"object","title":"Body_upload_campaign_resume_api_interview_join__token__resume_post"},"Body_upload_candidate_resume_api_candidates__candidate_id__upload_resume_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_candidate_resume_api_candidates__candidate_id__upload_resume_post"},"Body_upload_jd_api_public_v1_uploads__token__jd_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_jd_api_public_v1_uploads__token__jd_post"},"Body_upload_jd_file_api_job_roles__job_role_id__upload_jd_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_jd_file_api_job_roles__job_role_id__upload_jd_post"},"Body_upload_logo_api_organizations__org_id__logo_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_logo_api_organizations__org_id__logo_post"},"Body_upload_pipeline_audio_api_test_data_pipelines__pipeline_id__upload_audio_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_pipeline_audio_api_test_data_pipelines__pipeline_id__upload_audio_post"},"Body_upload_resumes_api_public_v1_uploads__token__resumes_post":{"properties":{"files":{"items":{"type":"string","format":"binary"},"type":"array","title":"Files"}},"type":"object","required":["files"],"title":"Body_upload_resumes_api_public_v1_uploads__token__resumes_post"},"Body_upload_transcript_for_test_api_interviews__interview_id__upload_transcript_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_transcript_for_test_api_interviews__interview_id__upload_transcript_post"},"BrainFinalizeResponse":{"properties":{"finished":{"type":"boolean","title":"Finished"},"brain_session_id":{"type":"string","title":"Brain Session Id"},"transcription_job_id":{"type":"string","title":"Transcription Job Id"}},"type":"object","required":["finished","brain_session_id","transcription_job_id"],"title":"BrainFinalizeResponse"},"BrainScheduleInterviewRequest":{"properties":{"candidate_name":{"type":"string","maxLength":200,"title":"Candidate Name"},"candidate_email":{"type":"string","maxLength":254,"title":"Candidate Email"},"role":{"type":"string","maxLength":200,"title":"Role","description":"Job title shown to the candidate. Used for the invitation email and dashboard label."},"job_role_id":{"type":"string","title":"Job Role Id","description":"UUID of a job role *with a competency rubric*. Brain interviews require a rubric to plan the conversation. POST /job-roles creates one (rubric extracted from the JD) and reports whether the role is interview-ready; GET /job-roles lists the ones you have. (A prior interview's `job_role_id` on GET /interviews/{id} works too.)"},"scheduled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled At","description":"Interview start time (ISO 8601 UTC). Must be in the future when given. Omit for an ON-DEMAND interview: the join link is live the moment it is created and the candidate starts when they click. Nothing downstream reads the slot — the join page never gates on it — so the only difference is what the record and the invite say.","examples":["2026-04-30T14:00:00Z"]},"duration_minutes":{"type":"integer","title":"Duration Minutes","description":"45, 60, or 90 minutes — on demand or scheduled alike. A behavioural interview for an entry-level role (experience_level `entry`) also accepts 30. Skill rounds stay at 45, 60, or 90.","default":60},"resume_text":{"type":"string","maxLength":50000,"minLength":50,"title":"Resume Text"},"framework_code":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Framework Code","description":"Interview framework (bei, cbi, topgrading). Defaults to the job role's framework."},"notes":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"Notes"},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata"},"send_invite":{"type":"boolean","title":"Send Invite","description":"Set false to skip BetterHire's invitation email and deliver `join_url` to the candidate yourself — for integrations that own the candidate conversation. The response reports `invite_sent`.","default":true},"return_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Return Url","description":"Only for organisations whose candidate journey is integrator-owned (Settings → Appearance → Candidate Journey). When the call ends the candidate's browser is sent here with `?interview_id=` appended, instead of BetterHire's thank-you screen. Must be https. Overrides the organisation's default return address for this interview. Same rules as `return_url` on POST /screening."},"skill_id":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Skill Id","description":"Optional: run this as a specialist SKILL round (e.g. 'system_design') instead of a behavioral interview. The brain conducts a collaborative skill assessment and scores it against that skill's rubric. GET /skills lists the valid ids with their names, aliases and rubrics. Omit for a standard behavioral interview."},"plan_round_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Plan Round Id","description":"Optional: the role assessment-plan round this interview fulfils (a round_id from the role's assessment_plan). The snapshot is derived server-side and stamped on the interview for progress display. Descriptive only — never gates scheduling order."},"use_earlier_round_questions":{"type":"boolean","title":"Use Earlier Round Questions","description":"Follow up on what earlier rounds found. When true, the AI interviewer is given this candidate's open questions for this job role from any earlier round (screening follow-ups, interview follow-ups, prep questions) and works the best of them into the interview, one per topic. `earlier_round_questions` in the response says how many were loaded. Behavioural interviews only: ignored, with a warning, when `skill_id` is set.","default":false}},"type":"object","required":["candidate_name","candidate_email","role","job_role_id","resume_text"],"title":"BrainScheduleInterviewRequest","description":"Schedule an AI-led brain interview.\n\nThe brain agent (powered by TheBetterHire's voice loop) IS the\ninterviewer — no human needed. The candidate joins via a token-based\nURL and the brain conducts a structured interview against the\njob role's competency rubric."},"BrainScheduleInterviewResponse":{"properties":{"interview_id":{"type":"string","title":"Interview Id"},"candidate_id":{"type":"string","title":"Candidate Id"},"status":{"type":"string","title":"Status","description":"'scheduled'."},"scheduled_at":{"type":"string","title":"Scheduled At"},"join_url":{"type":"string","title":"Join Url","description":"URL the candidate uses to join the brain interview — at the scheduled time, or at once when `on_demand` is true."},"duration_minutes":{"type":"integer","title":"Duration Minutes"},"message":{"type":"string","title":"Message"},"invite_sent":{"type":"boolean","title":"Invite Sent","description":"Whether BetterHire emailed the candidate the join link. False when `send_invite` was false, when the email could not be sent, and always in the sandbox — deliver `join_url` yourself in those cases.","default":false},"on_demand":{"type":"boolean","title":"On Demand","description":"True when the request carried no `scheduled_at`: the join link is live now, the record holds no slot, and `scheduled_at` above is the moment it went live.","default":false},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings","description":"Things the interview will do that you may not expect; the request still succeeded. Today: a `skill_id` that is not marked for entry-level roles, booked on an entry-level role (GET /skills `suitable_for_entry`). The round runs, calibrated to entry level. Also: `use_earlier_round_questions` found no open questions, or was ignored because `skill_id` is set."},"earlier_round_questions":{"type":"integer","title":"Earlier Round Questions","description":"How many open questions from earlier rounds were loaded for the AI interviewer (`use_earlier_round_questions`). 0 when not asked.","default":0}},"type":"object","required":["interview_id","candidate_id","status","scheduled_at","join_url","duration_minutes","message"],"title":"BrainScheduleInterviewResponse","description":"Response after scheduling a brain interview."},"BrainStartRequest":{"properties":{"resume_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Text"},"jd_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Text"}},"type":"object","title":"BrainStartRequest"},"BrainStartResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"first_question":{"type":"string","title":"First Question"},"stage_id":{"type":"string","title":"Stage Id"},"mode":{"type":"string","title":"Mode"},"plan_summary":{"type":"object","title":"Plan Summary"}},"type":"object","required":["session_id","first_question","stage_id","mode"],"title":"BrainStartResponse"},"BrainStateResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"candidate_name":{"type":"string","title":"Candidate Name"},"role_title":{"type":"string","title":"Role Title"},"framework":{"type":"string","title":"Framework"},"current_stage_id":{"type":"string","title":"Current Stage Id"},"finished":{"type":"boolean","title":"Finished"},"turn_count":{"type":"integer","title":"Turn Count"},"duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Minutes"},"stage_ids":{"items":{"type":"string"},"type":"array","title":"Stage Ids"}},"type":"object","required":["session_id","candidate_name","role_title","framework","current_stage_id","finished","turn_count"],"title":"BrainStateResponse"},"BrainTurnRequest":{"properties":{"candidate_utterance":{"type":"string","minLength":1,"title":"Candidate Utterance"}},"type":"object","required":["candidate_utterance"],"title":"BrainTurnRequest"},"BrainTurnResponse":{"properties":{"next_question":{"type":"string","title":"Next Question"},"stage_id":{"type":"string","title":"Stage Id"},"mode":{"type":"string","title":"Mode"},"finished":{"type":"boolean","title":"Finished","default":false}},"type":"object","required":["next_question","stage_id","mode"],"title":"BrainTurnResponse"},"CallEndedRequest":{"properties":{"call_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Call Id"},"transcript":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript"},"messages":{"anyOf":[{"items":{"type":"object"},"type":"array"},{"type":"null"}],"title":"Messages"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds"}},"type":"object","title":"CallEndedRequest","description":"Handle call ended event from frontend (fallback for webhooks)"},"CampaignClaimBody":{"properties":{"delegate":{"type":"boolean","title":"Delegate","default":false},"nominated_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nominated Email"}},"type":"object","title":"CampaignClaimBody"},"CampaignInviteBody":{"properties":{"emails":{"items":{"type":"string"},"type":"array","title":"Emails"}},"type":"object","required":["emails"],"title":"CampaignInviteBody"},"CancelInterviewResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status","description":"'cancelled'."},"message":{"type":"string","title":"Message"}},"type":"object","required":["id","status","message"],"title":"CancelInterviewResponse"},"CandidateBranding":{"properties":{"theme":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Theme"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"}},"type":"object","title":"CandidateBranding","description":"What the lobby may take from an org's branding. Filtered server-side;\nthis endpoint is public."},"CandidateCreate":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"name":{"type":"string","title":"Name"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"linkedin_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linkedin Url"},"github_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Github Url"},"portfolio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Portfolio Url"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"source_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Details"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"}},"type":"object","required":["email","name"],"title":"CandidateCreate","description":"Request model for creating a candidate"},"CandidateDecisionRequest":{"properties":{"decision":{"type":"string","title":"Decision"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["decision"],"title":"CandidateDecisionRequest"},"CandidateFeedback":{"properties":{"gap":{"type":"string","maxLength":240,"minLength":10,"title":"Gap"},"habit":{"$ref":"#/components/schemas/HabitFeedback"},"best_moment":{"$ref":"#/components/schemas/BestMoment"},"competency_lines":{"items":{"$ref":"#/components/schemas/CompetencyLine"},"type":"array","title":"Competency Lines"},"heard":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Heard"},"rewrite":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rewrite"},"structure":{"anyOf":[{"$ref":"#/components/schemas/Structure"},{"type":"null"}]}},"type":"object","required":["gap","habit","best_moment","competency_lines"],"title":"CandidateFeedback","description":"The stored/returned pieces: the draft plus the reserved field and,\nfrom contract 2.0, the story list."},"CandidateFeedbackRequest":{"properties":{"self_read":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Self Read","description":"The candidate's own read of how it went, written before seeing any result. When present, the feedback's `heard` sets what the interviewer actually heard against it.","examples":["I think the system design part went well but I rambled on pricing."]},"regenerate":{"type":"boolean","title":"Regenerate","description":"Discard the stored feedback and generate again. Billed again. Without it a second call returns the stored object for free.","default":false},"async":{"type":"boolean","title":"Async","description":"Answer 202 at once and generate in the background, instead of holding the request open for the generation (typically 20-60 seconds). When it is done, the `candidate_feedback.completed` webhook fires; or poll GET (409 `FEEDBACK_IN_PROGRESS` while it runs). A stored result, the sandbox and every error still answer at once.","default":false}},"type":"object","title":"CandidateFeedbackRequest"},"CandidateFeedbackResult":{"properties":{"interview_id":{"type":"string","title":"Interview Id"},"status":{"type":"string","enum":["ready","unavailable"],"title":"Status"},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At"},"unavailable_reason":{"anyOf":[{"type":"string","enum":["not_scored","generation_failed"]},{"type":"null"}],"title":"Unavailable Reason"},"feedback":{"anyOf":[{"$ref":"#/components/schemas/CandidateFeedback"},{"type":"null"}]},"contract_version":{"type":"string","title":"Contract Version","default":"1.0"},"scorecard_fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scorecard Fingerprint"}},"type":"object","required":["interview_id","status"],"title":"CandidateFeedbackResult","description":"The stored and returned object."},"CandidateInfo":{"properties":{"id":{"type":"string","title":"Id","description":"Candidate ID. Use for subsequent interviews and debriefs."},"name":{"type":"string","title":"Name","description":"Candidate full name."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Candidate email."}},"type":"object","required":["id","name"],"title":"CandidateInfo","description":"Candidate identification."},"CandidateJourneyInfo":{"properties":{"mode":{"type":"string","title":"Mode","default":"hiring"},"presenter_name":{"type":"string","title":"Presenter Name","default":""},"return_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return Url"}},"type":"object","title":"CandidateJourneyInfo","description":"Who owns what the candidate sees after the call (0003 §6b). Resolved\nfrom the org's setting (parent org for a sandbox) — see\ncore.domains.org.candidate_journey. Always present; hiring is the default\nevery org has today."},"CandidateRanking":{"properties":{"rank":{"type":"integer","title":"Rank","description":"Rank position (1 = highest score)."},"candidate_id":{"type":"string","title":"Candidate Id","description":"Candidate ID."},"name":{"type":"string","title":"Name","description":"Candidate name."},"overall_score":{"type":"integer","title":"Overall Score","description":"Overall score 0-100."},"recommendation":{"type":"string","title":"Recommendation","description":"strong_hire | hire | maybe | no_hire"},"confidence":{"type":"string","title":"Confidence","description":"high | medium | low"},"rounds_completed":{"type":"integer","title":"Rounds Completed","description":"Number of interview rounds completed."},"top_strengths":{"items":{"type":"string"},"type":"array","title":"Top Strengths"},"key_concerns":{"items":{"type":"string"},"type":"array","title":"Key Concerns"}},"type":"object","required":["rank","candidate_id","name","overall_score","recommendation","confidence","rounds_completed"],"title":"CandidateRanking"},"CandidateRunningTotalResponse":{"properties":{"candidate_id":{"type":"string","title":"Candidate Id"},"total_cents":{"type":"integer","title":"Total Cents"},"cap_cents":{"type":"integer","title":"Cap Cents"},"cap_remaining_cents":{"type":"integer","title":"Cap Remaining Cents"},"actions":{"items":{"type":"object"},"type":"array","title":"Actions"}},"type":"object","required":["candidate_id","total_cents","cap_cents","cap_remaining_cents","actions"],"title":"CandidateRunningTotalResponse","description":"Running cost for a single candidate's pipeline.\n\nUsed by GET /api/billing/candidates/{candidate_id}/total to drive\nthe per-candidate cost widget (PipelineCostBadge)."},"CandidateSearchResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/CandidateSearchResult"},"type":"array","title":"Candidates"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["candidates","total"],"title":"CandidateSearchResponse"},"CandidateSearchResult":{"properties":{"candidate_id":{"type":"string","title":"Candidate Id"},"name":{"type":"string","title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"interviews":{"items":{"$ref":"#/components/schemas/InterviewSummary"},"type":"array","title":"Interviews","default":[]},"interview_count":{"type":"integer","title":"Interview Count","default":0},"latest_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latest Score"},"latest_recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Latest Recommendation"}},"type":"object","required":["candidate_id","name"],"title":"CandidateSearchResult"},"CandidateUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"linkedin_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linkedin Url"},"github_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Github Url"},"portfolio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Portfolio Url"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"source_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Details"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"}},"type":"object","title":"CandidateUpdate","description":"Request model for updating a candidate"},"Certification":{"properties":{"name":{"type":"string","title":"Name"},"issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuer"},"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date"},"expiry_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiry Date"},"credential_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credential Id"}},"type":"object","required":["name"],"title":"Certification"},"ChangePasswordRequest":{"properties":{"current_password":{"type":"string","title":"Current Password"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordRequest","description":"Change password for authenticated user"},"CheckoutSessionRequest":{"properties":{"price_id":{"type":"string","title":"Price Id"},"success_url":{"type":"string","title":"Success Url"},"cancel_url":{"type":"string","title":"Cancel Url"}},"type":"object","required":["price_id","success_url","cancel_url"],"title":"CheckoutSessionRequest"},"CheckoutSessionResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"checkout_url":{"type":"string","title":"Checkout Url"}},"type":"object","required":["session_id","checkout_url"],"title":"CheckoutSessionResponse"},"ClipWindow":{"properties":{"start_seconds":{"type":"number","title":"Start Seconds"},"end_seconds":{"type":"number","title":"End Seconds"}},"type":"object","required":["start_seconds","end_seconds"],"title":"ClipWindow","description":"The window a clip URL covers, echoed back rounded to milliseconds."},"CodingFinishRequest":{"properties":{"problem_id":{"type":"string","title":"Problem Id"},"code":{"type":"string","title":"Code"},"outcome":{"type":"string","title":"Outcome"},"activity":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Activity"}},"type":"object","required":["problem_id","code","outcome"],"title":"CodingFinishRequest"},"CodingRunRequest":{"properties":{"problem_id":{"type":"string","title":"Problem Id"},"code":{"type":"string","title":"Code"},"activity":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Activity"}},"type":"object","required":["problem_id","code"],"title":"CodingRunRequest"},"CommunicationAnalysis":{"properties":{"fluency":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fluency","description":"Fluency score 0-100.","examples":[63]},"fillers_per_min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fillers Per Min","description":"Filler words per minute.","examples":[1.6]},"avg_pause_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Pause Seconds","description":"Average pause duration in seconds.","examples":[7.4]},"words_per_minute":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Words Per Minute","description":"Speaking pace.","examples":[152]},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds","description":"Total interview duration in seconds.","examples":[438]},"word_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Word Count","description":"Total words spoken by candidate.","examples":[1109]},"rating":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating","description":"Overall communication rating.","examples":["excellent"]},"filler_words":{"items":{"$ref":"#/components/schemas/FillerWord"},"type":"array","title":"Filler Words","description":"Individual filler word counts."}},"type":"object","title":"CommunicationAnalysis","description":"Speech and communication quality metrics from the interview."},"CommunicationEntry":{"properties":{"candidate_id":{"type":"string","title":"Candidate Id"},"name":{"type":"string","title":"Name"},"fluency":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fluency"},"wpm":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Wpm"},"fillers_per_min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fillers Per Min"},"trend":{"type":"string","title":"Trend","description":"improving | stable | declining | unknown","default":"unknown"}},"type":"object","required":["candidate_id","name"],"title":"CommunicationEntry"},"CommunicationTrend":{"properties":{"fluency":{"anyOf":[{"$ref":"#/components/schemas/TrendData"},{"type":"null"}]},"confidence":{"anyOf":[{"$ref":"#/components/schemas/TrendData"},{"type":"null"}]},"clarity":{"anyOf":[{"$ref":"#/components/schemas/TrendData"},{"type":"null"}]},"trend":{"type":"string","title":"Trend","description":"'improving', 'declining', or 'stable'.","default":"stable"}},"type":"object","title":"CommunicationTrend"},"CompareRequest":{"properties":{"candidate_ids":{"items":{"type":"string"},"type":"array","maxItems":4,"minItems":2,"title":"Candidate Ids","description":"List of 2-4 candidate IDs to compare. All must belong to your organization."}},"type":"object","required":["candidate_ids"],"title":"CompareRequest","description":"Compare 2-3 candidates side by side."},"CompareResponse":{"properties":{"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Primary role being compared."},"candidates_compared":{"type":"integer","title":"Candidates Compared","description":"Number of candidates in comparison."},"ranking":{"items":{"$ref":"#/components/schemas/CandidateRanking"},"type":"array","title":"Ranking","description":"Candidates ranked by overall score (highest first)."},"competency_matrix":{"items":{"$ref":"#/components/schemas/CompetencyMatrixEntry"},"type":"array","title":"Competency Matrix","description":"Each competency with per-candidate scores. 'best' marks the winner."},"head_to_head":{"allOf":[{"$ref":"#/components/schemas/HeadToHead"}],"description":"Fair comparison on overlapping rounds only (rounds all candidates completed)."},"communication":{"items":{"$ref":"#/components/schemas/CommunicationEntry"},"type":"array","title":"Communication","description":"Communication metrics per candidate."},"risk_comparison":{"items":{"$ref":"#/components/schemas/RiskEntry"},"type":"array","title":"Risk Comparison","description":"Risk level per candidate."},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings","description":"Warnings about comparison fairness (e.g., different round counts)."},"ai_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Summary","description":"AI-generated comparison narrative."}},"type":"object","required":["candidates_compared","ranking","head_to_head"],"title":"CompareResponse","description":"Side-by-side candidate comparison with ranking, competency matrix, and head-to-head."},"Competency":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"importance":{"type":"integer","title":"Importance"},"evaluation_criteria":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluation Criteria"}},"type":"object","required":["name","description","importance"],"title":"Competency"},"CompetencyAverage-Input":{"properties":{"name":{"type":"string","title":"Name"},"average":{"type":"number","title":"Average"}},"type":"object","required":["name","average"],"title":"CompetencyAverage"},"CompetencyAverage-Output":{"properties":{"name":{"type":"string","title":"Name"},"avg":{"type":"number","title":"Avg"},"n":{"type":"integer","title":"N"}},"type":"object","required":["name","avg","n"],"title":"CompetencyAverage","description":"Per-competency aggregate row inside the role benchmark."},"CompetencyBase":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string","pattern":"^(technical|behavioral|leadership|functional|culture|custom)$"},{"type":"null"}],"title":"Category"},"evaluation_criteria":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluation Criteria"},"sample_questions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sample Questions","default":[]}},"type":"object","required":["name"],"title":"CompetencyBase"},"CompetencyCoverage":{"properties":{"questions_count":{"type":"integer","title":"Questions Count"},"coverage":{"type":"string","title":"Coverage"}},"type":"object","required":["questions_count","coverage"],"title":"CompetencyCoverage","description":"Coverage of a competency"},"CompetencyCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string","pattern":"^(technical|behavioral|leadership|functional|culture|custom)$"},{"type":"null"}],"title":"Category"},"evaluation_criteria":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluation Criteria"},"sample_questions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sample Questions","default":[]}},"type":"object","required":["name"],"title":"CompetencyCreate"},"CompetencyHeatmapEntry":{"properties":{"name":{"type":"string","title":"Name","description":"Competency name."},"scores":{"type":"object","title":"Scores","description":"Scores per round (0-10 scale). Keys are round names."},"average":{"type":"number","title":"Average","description":"Average score across rounds (0-10)."}},"type":"object","required":["name","scores","average"],"title":"CompetencyHeatmapEntry"},"CompetencyInput":{"properties":{"name":{"type":"string","maxLength":100,"title":"Name","examples":["Distributed systems design"]},"description":{"type":"string","maxLength":2000,"title":"Description","description":"What good looks like. Drives the interviewer's probing questions, so it needs at least 20 characters of substance."},"importance":{"type":"integer","maximum":5.0,"minimum":1.0,"title":"Importance","description":"1–5. Competencies rated 4 or 5 are must-cover: the interviewer will not skip them, so each one costs interview time.","default":3},"evaluation_criteria":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Evaluation Criteria"}},"type":"object","required":["name","description"],"title":"CompetencyInput","description":"One competency the interviewer will assess and score."},"CompetencyItem":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"importance":{"type":"integer","title":"Importance"},"evaluation_criteria":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluation Criteria"}},"type":"object","required":["name","description","importance"],"title":"CompetencyItem","description":"Individual competency/skill to evaluate"},"CompetencyLine":{"properties":{"name":{"type":"string","title":"Name"},"line":{"type":"string","maxLength":200,"minLength":5,"title":"Line"}},"type":"object","required":["name","line"],"title":"CompetencyLine"},"CompetencyListResponse":{"properties":{"competencies":{"items":{"$ref":"#/components/schemas/CompetencyResponse"},"type":"array","title":"Competencies"},"total":{"type":"integer","title":"Total"},"categories":{"items":{"type":"string"},"type":"array","title":"Categories"}},"type":"object","required":["competencies","total","categories"],"title":"CompetencyListResponse"},"CompetencyMatrixEntry":{"properties":{"competency":{"type":"string","title":"Competency","description":"Competency name."},"scores":{"type":"object","title":"Scores","description":"Candidate ID → score (0-10). Null if not assessed."},"best":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Best","description":"Candidate ID with highest score."}},"type":"object","required":["competency","scores"],"title":"CompetencyMatrixEntry"},"CompetencyResponse":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string","pattern":"^(technical|behavioral|leadership|functional|culture|custom)$"},{"type":"null"}],"title":"Category"},"evaluation_criteria":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluation Criteria"},"sample_questions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sample Questions","default":[]},"id":{"type":"string","title":"Id"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"},"is_system_default":{"type":"boolean","title":"Is System Default"},"source":{"type":"string","title":"Source"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["name","id","is_system_default","source","is_active"],"title":"CompetencyResponse"},"CompetencyScore":{"properties":{"name":{"type":"string","title":"Name","description":"Competency name.","examples":["Team Leadership"]},"score":{"type":"integer","title":"Score","description":"Score 0-100.","examples":[90]},"assessment":{"type":"string","title":"Assessment","description":"Assessment badge: 'Excellent', 'Strong', 'Good', 'Moderate', 'Needs Work'.","examples":["Excellent"]},"reasoning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reasoning","description":"AI reasoning for this score."},"evidence":{"items":{"$ref":"#/components/schemas/EvidenceItem"},"type":"array","title":"Evidence","description":"Timestamped quotes from the transcript supporting this score."},"concerns":{"items":{"type":"string"},"type":"array","title":"Concerns","description":"Specific concerns identified for this competency."},"coverage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage","description":"How much of this competency the interview actually reached: 'covered', 'partial', 'asserted_only', 'resume_only' or 'not_reached'. Null on analyses that predate coverage tracking.","examples":["covered"]},"follow_up_questions":{"items":{"type":"string"},"type":"array","title":"Follow Up Questions","description":"Questions the interviewer would have asked next on this competency (AI screenings). Empty where the analysis carries none."}},"type":"object","required":["name","score","assessment"],"title":"CompetencyScore","description":"Assessment of a single competency with evidence."},"CompetencyUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"evaluation_criteria":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluation Criteria"},"sample_questions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sample Questions"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"CompetencyUpdate"},"ConfirmSpeakerRolesRequest":{"properties":{"candidate_speaker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Speaker"},"candidate_not_present":{"type":"boolean","title":"Candidate Not Present","default":false}},"type":"object","title":"ConfirmSpeakerRolesRequest","description":"Resolve a pending speaker-role confirmation (Phase 2 fail-loud —\ndocs/PLAN-human-interview-recall-diarization.md). Exactly one of:\n  - candidate_speaker: display name of the participant the recruiter\n    identifies as the candidate (must match a transcript speaker),\n  - candidate_not_present: true — candidate never joined; the\n    interview is voided (reason candidate_no_show), never scored."},"ConfirmSpeakerRolesResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"interview_id":{"type":"string","title":"Interview Id"},"speaker_roles_needs_confirmation":{"type":"boolean","title":"Speaker Roles Needs Confirmation"},"candidate_speaker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Speaker"},"candidate_not_present":{"type":"boolean","title":"Candidate Not Present","default":false},"roles":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Roles"},"analysis_queued":{"type":"boolean","title":"Analysis Queued","default":false},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["success","interview_id","speaker_roles_needs_confirmation"],"title":"ConfirmSpeakerRolesResponse"},"ContactBody":{"properties":{"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"email_verified":{"type":"boolean","title":"Email Verified","default":false}},"type":"object","title":"ContactBody"},"ContactCreate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"is_primary":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Primary"}},"type":"object","title":"ContactCreate"},"ContactUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"is_primary":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Primary"}},"type":"object","title":"ContactUpdate"},"CreateSessionRequest":{"properties":{"role":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Role"},"source":{"type":"string","title":"Source","description":"Upload source: mcp, dashboard, api","default":"mcp"}},"type":"object","title":"CreateSessionRequest"},"CreateSessionResponse":{"properties":{"token":{"type":"string","title":"Token"},"upload_url":{"type":"string","title":"Upload Url"},"expires_at":{"type":"string","title":"Expires At"}},"type":"object","required":["token","upload_url","expires_at"],"title":"CreateSessionResponse"},"CreateTokenRequest":{"properties":{"resource_type":{"type":"string","title":"Resource Type","description":"Type: interview_report, debrief_report"},"resource_id":{"type":"string","title":"Resource Id","description":"Interview ID or candidate ID"}},"type":"object","required":["resource_type","resource_id"],"title":"CreateTokenRequest"},"CreateTokenResponse":{"properties":{"download_url":{"type":"string","title":"Download Url"},"expires_in_minutes":{"type":"integer","title":"Expires In Minutes"}},"type":"object","required":["download_url","expires_in_minutes"],"title":"CreateTokenResponse"},"DashboardConfigPatch":{"properties":{"config":{"type":"object","title":"Config"}},"type":"object","required":["config"],"title":"DashboardConfigPatch","description":"PATCH body: a partial config to deep-merge into the stored one."},"DebriefResponse":{"properties":{"candidate_id":{"type":"string","title":"Candidate Id","description":"Candidate ID."},"candidate_name":{"type":"string","title":"Candidate Name","description":"Candidate name."},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Primary role across interviews."},"overall_score":{"type":"integer","title":"Overall Score","description":"Average score across all rounds (0-100)."},"recommendation":{"type":"string","title":"Recommendation","description":"Consensus hiring recommendation."},"confidence":{"type":"string","title":"Confidence","description":"Confidence level in recommendation."},"rounds_completed":{"type":"integer","title":"Rounds Completed","description":"Number of interview rounds included."},"date_range":{"type":"string","title":"Date Range","description":"Date range of interviews.","default":"","examples":["Jan 23 - Feb 3"]},"executive_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Executive Summary","description":"AI-generated executive brief (3-4 sentences)."},"interview_journey":{"items":{"$ref":"#/components/schemas/RoundOutcome"},"type":"array","title":"Interview Journey","description":"Per-round scores and recommendations in chronological order."},"competency_heatmap":{"items":{"$ref":"#/components/schemas/CompetencyHeatmapEntry"},"type":"array","title":"Competency Heatmap","description":"Competency scores across all rounds (0-10 scale)."},"top_strengths":{"items":{"$ref":"#/components/schemas/AggregatedItem"},"type":"array","title":"Top Strengths","description":"Strengths ranked by frequency across rounds."},"key_concerns":{"items":{"$ref":"#/components/schemas/AggregatedItem"},"type":"array","title":"Key Concerns","description":"Concerns ranked by frequency across rounds."},"communication_trend":{"anyOf":[{"$ref":"#/components/schemas/CommunicationTrend"},{"type":"null"}],"description":"Communication metrics trend across rounds."},"best_evidence":{"items":{"$ref":"#/components/schemas/EvidenceHighlight"},"type":"array","title":"Best Evidence","description":"Top 3 evidence quotes with attribution."},"risk_assessment":{"anyOf":[{"$ref":"#/components/schemas/RiskAssessment"},{"type":"null"}],"description":"Consolidated risk assessment."},"final_recommendation":{"anyOf":[{"$ref":"#/components/schemas/FinalRecommendation"},{"type":"null"}],"description":"Final hiring recommendation with confidence and next step."}},"type":"object","required":["candidate_id","candidate_name","overall_score","recommendation","confidence","rounds_completed"],"title":"DebriefResponse","description":"Cross-interview synthesis report — the hiring decision package."},"DeleteResponse":{"properties":{"id":{"type":"string","title":"Id","description":"ID of the deleted interview."},"deleted":{"type":"boolean","title":"Deleted","description":"Whether the deletion was successful."},"message":{"type":"string","title":"Message","description":"Human-readable confirmation."}},"type":"object","required":["id","deleted","message"],"title":"DeleteResponse","description":"Confirmation of interview data deletion."},"DomainAddRequest":{"properties":{"domain":{"type":"string","title":"Domain","default":""}},"type":"object","title":"DomainAddRequest"},"Education":{"properties":{"institution":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Institution"},"degree":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Degree"},"field_of_study":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field Of Study"},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"gpa":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gpa"},"honors":{"items":{"type":"string"},"type":"array","title":"Honors","default":[]}},"type":"object","title":"Education"},"EvaluationCreate":{"properties":{"interview_id":{"type":"string","title":"Interview Id"},"application_id":{"type":"string","title":"Application Id"},"interview_stage_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Stage Id"},"overall_rating":{"type":"integer","title":"Overall Rating"},"competency_scores":{"anyOf":[{},{"type":"null"}],"title":"Competency Scores"},"feedback_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback Text"},"strengths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Strengths"},"weaknesses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Weaknesses"},"recommendation":{"type":"string","title":"Recommendation"},"evidence":{"anyOf":[{},{"type":"null"}],"title":"Evidence"}},"type":"object","required":["interview_id","application_id","overall_rating","recommendation"],"title":"EvaluationCreate","description":"POST body. Required set mirrors the old route (interview_id,\napplication_id, overall_rating, recommendation). overall_rating is a\nrequired int so 0 is accepted but omission is rejected — matching the old\n`=== undefined` check. evaluator_type/evaluator_user_id are server-set."},"EvaluationRow":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"interview_id":{"type":"string","title":"Interview Id"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"interview_stage_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Stage Id"},"evaluator_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluator Type"},"evaluator_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluator User Id"},"evaluator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluator Name"},"evaluator_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluator Model"},"overall_rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Overall Rating"},"competency_scores":{"anyOf":[{},{"type":"null"}],"title":"Competency Scores"},"feedback_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback Text"},"strengths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Strengths"},"weaknesses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Weaknesses"},"key_highlights":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Key Highlights"},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation"},"recommendation_reasoning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation Reasoning"},"evidence":{"anyOf":[{},{"type":"null"}],"title":"Evidence"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"}},"type":"object","required":["id","organization_id","interview_id"],"title":"EvaluationRow","description":"Full evaluations row (SELECT * / RETURNING *). Every column declared so\nnothing silently drops when a column is added."},"EvaluationUpdate":{"properties":{"overall_rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Overall Rating"},"competency_scores":{"anyOf":[{},{"type":"null"}],"title":"Competency Scores"},"feedback_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback Text"},"strengths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Strengths"},"weaknesses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Weaknesses"},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation"},"evidence":{"anyOf":[{},{"type":"null"}],"title":"Evidence"}},"type":"object","title":"EvaluationUpdate","description":"PUT body. Field set mirrors the old route's whitelist exactly."},"EvidenceHighlight":{"properties":{"quote":{"type":"string","title":"Quote","description":"Direct quote from the candidate."},"round":{"type":"string","title":"Round","description":"Which interview round."},"competency":{"type":"string","title":"Competency","description":"Which competency this evidences."},"score":{"type":"number","title":"Score","description":"Score for this competency (0-10)."}},"type":"object","required":["quote","round","competency","score"],"title":"EvidenceHighlight"},"EvidenceItem":{"properties":{"text":{"type":"string","title":"Text","description":"Direct quote from the transcript."},"speaker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker","description":"Who said it: 'candidate' or 'interviewer'."},"start_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Start Seconds","description":"Start time in seconds into the recording. Use with recording_url to play this moment."},"end_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"End Seconds","description":"End time in seconds into the recording."}},"type":"object","required":["text"],"title":"EvidenceItem","description":"A timestamped evidence quote from the interview transcript."},"EvidenceRef":{"properties":{"competency":{"type":"string","title":"Competency","description":"Competency name exactly as on the scorecard."},"evidence":{"type":"integer","minimum":0.0,"title":"Evidence","description":"Index into that competency's evidence[]."}},"type":"object","required":["competency","evidence"],"title":"EvidenceRef","description":"Points at one quote on the public scorecard."},"ExecutiveSummaryRequest":{"properties":{"candidate_name":{"type":"string","title":"Candidate Name"},"job_role":{"type":"string","title":"Job Role"},"overall_score":{"type":"integer","title":"Overall Score"},"consensus_recommendation":{"type":"string","title":"Consensus Recommendation"},"total_rounds":{"type":"integer","title":"Total Rounds"},"round_summaries":{"items":{"$ref":"#/components/schemas/RoundSummary"},"type":"array","title":"Round Summaries"},"top_strengths":{"items":{"$ref":"#/components/schemas/StrengthConcern"},"type":"array","title":"Top Strengths"},"key_concerns":{"items":{"$ref":"#/components/schemas/StrengthConcern"},"type":"array","title":"Key Concerns"},"competency_averages":{"items":{"$ref":"#/components/schemas/CompetencyAverage-Input"},"type":"array","title":"Competency Averages"},"communication_trend":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Communication Trend"}},"type":"object","required":["candidate_name","job_role","overall_score","consensus_recommendation","total_rounds","round_summaries","top_strengths","key_concerns","competency_averages"],"title":"ExecutiveSummaryRequest","description":"Request model for generating executive summary"},"ExecutiveSummaryResponse":{"properties":{"executive_brief":{"type":"string","title":"Executive Brief"},"generated_at":{"type":"string","title":"Generated At"},"provider":{"type":"string","title":"Provider"},"tokens_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tokens Used"}},"type":"object","required":["executive_brief","generated_at","provider"],"title":"ExecutiveSummaryResponse","description":"Response model for executive summary"},"ExpandCompetencyRequest":{"properties":{"name":{"type":"string","title":"Name"},"job_title":{"type":"string","title":"Job Title"},"jd_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Text"}},"type":"object","required":["name","job_title"],"title":"ExpandCompetencyRequest","description":"AI-fill the description + evaluation criteria for a competency the\nrecruiter typed in by hand. Validates the name first — the LLM\nrejects gibberish (\"Potato\"), profanity, and obviously off-topic\nnames so the form never gets garbage filled in for it."},"ExpandCompetencyResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"evaluation_criteria":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evaluation Criteria"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["success"],"title":"ExpandCompetencyResponse","description":"Either a successful expansion (description + criteria) or a clear\nrejection so the UI can surface a specific reason instead of a\ngeneric error."},"ExtractCandidateInfoResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"resume_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Text"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["success","name","email","phone"],"title":"ExtractCandidateInfoResponse","description":"Response with extracted candidate information from resume"},"ExtractCompetenciesRequest":{"properties":{"jd_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Text"},"job_role_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Role Id"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"}},"type":"object","title":"ExtractCompetenciesRequest","description":"Request to extract competencies from JD"},"ExtractCompetenciesResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"competencies":{"items":{"$ref":"#/components/schemas/CompetencyItem"},"type":"array","title":"Competencies"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"jd_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Text"}},"type":"object","required":["success","competencies"],"title":"ExtractCompetenciesResponse","description":"Response with extracted competencies"},"FaceFrameResponse":{"properties":{"url":{"type":"string","title":"Url","description":"Time-limited URL to a JPEG. Fetch it once and keep your own copy if you show it beyond the hour (e.g. in a link preview).","examples":["https://audio.thebetterhire.ai/.../frames/face-v1.jpg?Signature=..."]},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the URL expires.","examples":[3600]},"width":{"type":"integer","title":"Width","description":"Image width in pixels.","examples":[1280]},"height":{"type":"integer","title":"Height","description":"Image height in pixels.","examples":[720]}},"type":"object","required":["url","expires_in","width","height"],"title":"FaceFrameResponse","description":"One still of the candidate's face from a video interview."},"FeedbackRequest":{"properties":{"rating":{"type":"integer","maximum":5.0,"minimum":1.0,"title":"Rating","description":"Rating from 1 (poor) to 5 (excellent)"},"comment":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Comment","description":"Optional feedback text"},"context":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Context","description":"What triggered this feedback (e.g. match_result, screening_result, error)"}},"type":"object","required":["rating"],"title":"FeedbackRequest"},"FillerWord":{"properties":{"word":{"type":"string","title":"Word","examples":["uh"]},"count":{"type":"integer","title":"Count","examples":[2]}},"type":"object","required":["word","count"],"title":"FillerWord","description":"A filler word and its count."},"FinalRecommendation":{"properties":{"recommendation":{"type":"string","title":"Recommendation","description":"Consensus: strong_hire | hire | maybe | no_hire"},"confidence":{"type":"string","title":"Confidence","description":"'high', 'medium', or 'low'."},"next_step":{"type":"string","title":"Next Step","description":"Recommended next action."},"key_question":{"type":"string","title":"Key Question","description":"Key discussion question for hiring panel.","default":""}},"type":"object","required":["recommendation","confidence","next_step"],"title":"FinalRecommendation"},"FollowUpItem":{"properties":{"topic":{"type":"string","title":"Topic"},"reason":{"type":"string","title":"Reason"}},"type":"object","required":["topic","reason"],"title":"FollowUpItem","description":"Item to follow up on from previous rounds"},"FormConfig":{"properties":{"job_roles":{"$ref":"#/components/schemas/FormFieldConfig"},"interviews":{"$ref":"#/components/schemas/FormFieldConfig"}},"type":"object","required":["job_roles","interviews"],"title":"FormConfig"},"FormConfigResponse":{"properties":{"form_config":{"$ref":"#/components/schemas/FormConfig"},"organization_id":{"type":"string","title":"Organization Id"},"organization_name":{"type":"string","title":"Organization Name"}},"type":"object","required":["form_config","organization_id","organization_name"],"title":"FormConfigResponse"},"FormConfigUpdateRequest":{"properties":{"job_roles":{"anyOf":[{"$ref":"#/components/schemas/FormFieldConfig"},{"type":"null"}]},"interviews":{"anyOf":[{"$ref":"#/components/schemas/FormFieldConfig"},{"type":"null"}]}},"type":"object","title":"FormConfigUpdateRequest"},"FormFieldConfig":{"properties":{"enabled_fields":{"items":{"type":"string"},"type":"array","title":"Enabled Fields"},"required_fields":{"items":{"type":"string"},"type":"array","title":"Required Fields"},"custom_fields":{"items":{"type":"object"},"type":"array","title":"Custom Fields","default":[]}},"type":"object","required":["enabled_fields","required_fields"],"title":"FormFieldConfig"},"FrameworkListResponse":{"properties":{"frameworks":{"items":{"$ref":"#/components/schemas/FrameworkResponse"},"type":"array","title":"Frameworks"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["frameworks","total"],"title":"FrameworkListResponse"},"FrameworkResponse":{"properties":{"id":{"type":"string","title":"Id"},"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"evaluation_criteria":{"type":"object","title":"Evaluation Criteria"},"report_sections":{"type":"object","title":"Report Sections"},"is_active":{"type":"boolean","title":"Is Active"},"display_order":{"type":"integer","title":"Display Order"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["id","code","name","evaluation_criteria","report_sections","is_active","display_order"],"title":"FrameworkResponse"},"GenerateAudioRequest":{"properties":{"transcript_json":{"type":"object","title":"Transcript Json"},"interviewer_voice":{"type":"string","title":"Interviewer Voice","default":"en-US-GuyNeural"},"candidate_voice":{"type":"string","title":"Candidate Voice","default":"en-US-JennyNeural"}},"type":"object","required":["transcript_json"],"title":"GenerateAudioRequest"},"GenerateJDPDFRequest":{"properties":{"job_title":{"type":"string","title":"Job Title"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"jd_text":{"type":"string","title":"Jd Text"}},"type":"object","required":["job_title","jd_text"],"title":"GenerateJDPDFRequest","description":"Request to generate a job description PDF"},"GenerateJDResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"job_description":{"type":"string","title":"Job Description"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["success","job_description"],"title":"GenerateJDResponse","description":"Response with generated job description"},"GenerateResumeRequest":{"properties":{"jd_text":{"type":"string","title":"Jd Text"},"quality":{"type":"string","title":"Quality","default":"strong"},"years_experience":{"type":"integer","title":"Years Experience","default":5},"key_skills":{"items":{"type":"string"},"type":"array","title":"Key Skills","default":[]}},"type":"object","required":["jd_text"],"title":"GenerateResumeRequest"},"GenerateTranscriptRequest":{"properties":{"jd_text":{"type":"string","title":"Jd Text"},"resume_text":{"type":"string","title":"Resume Text"},"framework":{"type":"string","title":"Framework","default":"bei"},"quality":{"type":"string","title":"Quality","default":"strong"},"length":{"type":"string","title":"Length","default":"medium"},"competencies":{"items":{"type":"string"},"type":"array","title":"Competencies","default":[]},"mode":{"type":"string","title":"Mode","default":"standard"},"screening_questions":{"items":{"type":"string"},"type":"array","title":"Screening Questions","default":[]}},"type":"object","required":["jd_text","resume_text"],"title":"GenerateTranscriptRequest"},"GenericEmailRequest":{"properties":{"to_email":{"type":"string","format":"email","title":"To Email"},"subject":{"type":"string","title":"Subject"},"html_content":{"type":"string","title":"Html Content"},"text_content":{"type":"string","title":"Text Content"},"email_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Type"},"ics_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ics Content"},"ics_filename":{"type":"string","title":"Ics Filename","default":"interview.ics"}},"type":"object","required":["to_email","subject","html_content","text_content"],"title":"GenericEmailRequest","description":"Request model for sending generic emails (screening invites, etc.).\n\nThe Next.js side renders both `html_content` and `text_content`\nvia `lib/email-templates.ts`. Historic bug: this endpoint accepted\n`text_content` but only shipped HTML, leaving every candidate\nemail HTML-only — a known spam-filter penalty. Now passed through."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HabitFeedback":{"properties":{"title":{"type":"string","maxLength":120,"minLength":8,"title":"Title"},"explanation":{"type":"string","maxLength":600,"minLength":20,"title":"Explanation"},"ref":{"$ref":"#/components/schemas/EvidenceRef"}},"type":"object","required":["title","explanation","ref"],"title":"HabitFeedback"},"HeadToHead":{"properties":{"overlapping_rounds":{"items":{"type":"string"},"type":"array","title":"Overlapping Rounds","description":"Round names all candidates have completed."},"per_round":{"items":{"$ref":"#/components/schemas/HeadToHeadRound"},"type":"array","title":"Per Round"}},"type":"object","title":"HeadToHead"},"HeadToHeadRound":{"properties":{"round":{"type":"string","title":"Round","description":"Round name."},"scores":{"type":"object","title":"Scores","description":"Candidate ID → {score, recommendation}"}},"type":"object","required":["round","scores"],"title":"HeadToHeadRound"},"HiringCompaniesResponse":{"properties":{"companies":{"items":{"$ref":"#/components/schemas/HiringCompanySuggestion"},"type":"array","title":"Companies"}},"type":"object","required":["companies"],"title":"HiringCompaniesResponse"},"HiringCompanyResponse":{"properties":{"display":{"type":"string","title":"Display"},"candidate_facing_name":{"type":"string","title":"Candidate Facing Name"},"pronounce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pronounce"},"visibility":{"type":"string","title":"Visibility"},"needs_review":{"type":"boolean","title":"Needs Review"}},"type":"object","required":["display","candidate_facing_name","pronounce","visibility","needs_review"],"title":"HiringCompanyResponse","description":"Resolver output exposed to FE.\n\nMirrors core.hiring_company_service.HiringCompany so the recruiter\nUI can render the preview line (\"Candidate will see: …\") and the\nneeds-review badge from a single source of truth."},"HiringCompanySuggestion":{"properties":{"display":{"type":"string","title":"Display"},"role_count":{"type":"integer","title":"Role Count"}},"type":"object","required":["display","role_count"],"title":"HiringCompanySuggestion"},"HiringDecisionCreate":{"properties":{"application_id":{"type":"string","title":"Application Id"},"decision":{"type":"string","title":"Decision"},"offer_details":{"anyOf":[{},{"type":"null"}],"title":"Offer Details"},"decision_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decision Notes"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"rejection_feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Feedback"},"decline_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decline Reason"},"offer_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Expires At"}},"type":"object","required":["application_id","decision"],"title":"HiringDecisionCreate","description":"POST body. Mirrors the old route's destructured fields. `decision` is NOT\nenum-validated here on purpose — the old route only checked presence and let\nthe DB CHECK constraint reject bad values (surfacing as a 500); preserving\nthat avoids a contract change (422). The DB still enforces the 6 valid\nvalues."},"HiringDecisionEnriched":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"application_id":{"type":"string","title":"Application Id"},"job_position_id":{"type":"string","title":"Job Position Id"},"decision":{"type":"string","title":"Decision"},"decided_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided By"},"decided_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Decided At"},"offer_details":{"anyOf":[{},{"type":"null"}],"title":"Offer Details"},"offer_extended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Extended At"},"offer_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Expires At"},"offer_responded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Responded At"},"offer_accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Accepted At"},"offer_declined_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Declined At"},"rejected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rejected At"},"withdrawn_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Withdrawn At"},"onboarding_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Onboarding Date"},"decision_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decision Notes"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"rejection_feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Feedback"},"decline_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decline Reason"},"decline_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decline Notes"},"feedback_shared_with_candidate":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Feedback Shared With Candidate"},"candidate_added_to_talent_pool":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Candidate Added To Talent Pool"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"},"decided_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided By Name"},"candidate_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Name"},"candidate_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Email"},"position_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Position Code"}},"type":"object","required":["id","organization_id","application_id","job_position_id","decision"],"title":"HiringDecisionEnriched","description":"GET response — the bare row plus the 4 LEFT JOIN columns the FE reads."},"HiringDecisionRow":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"application_id":{"type":"string","title":"Application Id"},"job_position_id":{"type":"string","title":"Job Position Id"},"decision":{"type":"string","title":"Decision"},"decided_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided By"},"decided_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Decided At"},"offer_details":{"anyOf":[{},{"type":"null"}],"title":"Offer Details"},"offer_extended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Extended At"},"offer_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Expires At"},"offer_responded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Responded At"},"offer_accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Accepted At"},"offer_declined_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Declined At"},"rejected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rejected At"},"withdrawn_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Withdrawn At"},"onboarding_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Onboarding Date"},"decision_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decision Notes"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"rejection_feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Feedback"},"decline_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decline Reason"},"decline_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decline Notes"},"feedback_shared_with_candidate":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Feedback Shared With Candidate"},"candidate_added_to_talent_pool":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Candidate Added To Talent Pool"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"}},"type":"object","required":["id","organization_id","application_id","job_position_id","decision"],"title":"HiringDecisionRow","description":"Full hiring_decisions row — what POST/PUT return (bare RETURNING *).\nEvery column is declared so nothing is silently dropped when a column is\nadded to the table."},"HiringDecisionUpdate":{"properties":{"decision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decision"},"offer_details":{"anyOf":[{},{"type":"null"}],"title":"Offer Details"},"decision_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decision Notes"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"rejection_feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Feedback"},"decline_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decline Reason"},"offer_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Expires At"},"offer_responded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Responded At"},"offer_accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Accepted At"},"offer_declined_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Declined At"},"onboarding_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Onboarding Date"}},"type":"object","title":"HiringDecisionUpdate","description":"PUT body. Field set mirrors the old route's whitelist (route.ts:69-81)\nEXACTLY — note it exposes 4 timestamp fields POST does not\n(offer_responded_at, offer_accepted_at, offer_declined_at, onboarding_date),\nwhich is how accept/decline/onboarding transitions are recorded."},"ImproveJDRequest":{"properties":{"jd_text":{"type":"string","title":"Jd Text"},"fix_instructions":{"items":{"type":"string"},"type":"array","title":"Fix Instructions"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"}},"type":"object","required":["jd_text","fix_instructions"],"title":"ImproveJDRequest","description":"Revise an existing JD to address a set of fixes selected from the\nquality scorecard. Used by Paste/Upload tabs where the recruiter's\nown JD text is the starting point — we don't regenerate from\nscratch (would lose their content), we surgically rewrite."},"InferredRole":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"The role title as a recruiter would write it, at most 120 characters; never a section heading or a line of punctuation. Null when the text does not name the role.","examples":["Full-Stack Software Engineer"]},"experience_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Experience Level","description":"entry, mid, senior, lead or executive, when the text says."},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name","description":"The hiring company, only when the text names one."},"skill_ids":{"items":{"type":"string"},"type":"array","title":"Skill Ids","description":"Specialist skill packs the posting genuinely calls for (ids from GET /skills). Usually empty."}},"type":"object","title":"InferredRole","description":"What the extraction read off the posting (platform request 0003 §6d).\nEvery field is validated in code; null means \"the text does not say\",\nnever a guess. Sandbox roles infer from labelled lines only."},"InjectPipelineRequest":{"properties":{"mode":{"type":"string","title":"Mode","default":"quick"}},"type":"object","title":"InjectPipelineRequest"},"InternalCompareRequest":{"properties":{"candidate_ids":{"items":{"type":"string"},"type":"array","maxItems":4,"minItems":2,"title":"Candidate Ids","description":"List of 2-4 candidate IDs to compare."}},"type":"object","required":["candidate_ids"],"title":"InternalCompareRequest"},"InterviewCompetencyOverride":{"properties":{"use_stage_defaults":{"type":"boolean","title":"Use Stage Defaults","default":true},"competency_ids":{"items":{"type":"string"},"type":"array","title":"Competency Ids","default":[]},"custom_competencies":{"items":{"$ref":"#/components/schemas/CompetencyBase"},"type":"array","title":"Custom Competencies","default":[]}},"type":"object","title":"InterviewCompetencyOverride","description":"Override competencies for a specific interview"},"InterviewContext":{"properties":{"round_number":{"type":"integer","title":"Round Number"},"job_title":{"type":"string","title":"Job Title"},"stage_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Name"},"focus_competencies":{"items":{"type":"string"},"type":"array","title":"Focus Competencies","default":[]}},"type":"object","required":["round_number","job_title"],"title":"InterviewContext","description":"Context about the current interview"},"InterviewCreateRequest":{"properties":{"candidate_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Candidate Id"},"candidate_name":{"type":"string","title":"Candidate Name"},"candidate_email":{"type":"string","format":"email","title":"Candidate Email"},"candidate_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Phone"},"candidate_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Ref"},"role":{"type":"string","title":"Role"},"job_role_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Job Role Id"},"interviewer_email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Interviewer Email"},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"scheduled_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled Timezone"},"duration_minutes":{"type":"integer","title":"Duration Minutes","default":45},"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url"},"meeting_platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Platform"},"interview_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Type"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"send_calendar_invites":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Send Calendar Invites","default":false},"is_test_mode":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Test Mode","default":false},"framework_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Framework Code"},"delivery_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivery Mode","default":"video_call"},"screening_question_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Screening Question Ids"},"resume_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Text"},"interviewer_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interviewer Type","default":"human"},"skill_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skill Id"},"plan_round_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Round Id"},"selected_question_bank_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Selected Question Bank Ids"},"voice_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Provider"}},"type":"object","required":["candidate_name","candidate_email","role"],"title":"InterviewCreateRequest","description":"Create interview request"},"InterviewDetailResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique interview ID."},"status":{"type":"string","title":"Status","description":"'pending', 'processing', 'completed', 'failed'."},"transcript_status":{"type":"string","title":"Transcript Status","description":"Transcription sub-status."},"analysis_status":{"type":"string","title":"Analysis Status","description":"Analysis sub-status."},"input_mode":{"type":"string","title":"Input Mode","description":"'meeting' or 'recording'."},"created_at":{"type":"string","title":"Created At","description":"ISO 8601 timestamp."},"candidate":{"allOf":[{"$ref":"#/components/schemas/CandidateInfo"}],"description":"Candidate info."},"role":{"type":"string","title":"Role","description":"Job role."},"scorecard":{"anyOf":[{"$ref":"#/components/schemas/Scorecard"},{"type":"null"}],"description":"AI-generated scorecard. Present when analysis is completed."},"transcript":{"anyOf":[{"$ref":"#/components/schemas/core__domains__public_api__scorecard__TranscriptResponse"},{"type":"null"}],"description":"Full transcript. Present when transcription is completed."},"recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Url","description":"URL of the interview recording. Use with evidence timestamps for playback."},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata","description":"Custom metadata from submission."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if failed."},"failure_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Reason","description":"Why the interview ended without a call, when it did. 'never_joined': nobody started it within its join window; 'cancelled_by_integrator': you cancelled it before it started. Both mean nothing was charged and nothing went wrong — read them as 'did not happen', not as a failure. Other values describe a meeting bot or processing failure. Null otherwise."},"joinable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Joinable","description":"For interviews the candidate joins by link: true while the link can still be started. False once started or finished, once cancelled, or once its join window passed. Null for meeting and recording interviews. Reuse the link while true; start a new interview when false."},"join_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Join Expires At","description":"For interviews created through this API: when the link stops working if nobody has started it — one hour after `scheduled_at`, or after creation when unbooked. Null when no window applies."},"transcript_quality":{"anyOf":[{"$ref":"#/components/schemas/TranscriptQualitySummary"},{"type":"null"}],"description":"Present once analysis completed. Tells 'too short to score' apart from 'still analysing' and from 'failed': a completed interview with `scorecard: null` and label 'partial' or 'invalid' was heard, but the candidate said too little to score."}},"type":"object","required":["id","status","transcript_status","analysis_status","input_mode","created_at","candidate","role"],"title":"InterviewDetailResponse","description":"Full interview detail with scorecard, transcript, and recording."},"InterviewJoinInfo":{"properties":{"interview_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Id"},"candidate_first_name":{"type":"string","title":"Candidate First Name"},"job_title":{"type":"string","title":"Job Title"},"company_name":{"type":"string","title":"Company Name"},"interview_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Type"},"interviewer_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interviewer Type"},"duration_minutes":{"type":"integer","title":"Duration Minutes","default":30},"status":{"type":"string","title":"Status"},"delivery_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivery Mode"},"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url"},"meeting_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Token"},"voice_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Provider"},"competencies":{"items":{"type":"string"},"type":"array","title":"Competencies","default":[]},"plan_ready":{"type":"boolean","title":"Plan Ready","default":false},"has_coding_stage":{"type":"boolean","title":"Has Coding Stage","default":false},"skill_problem":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skill Problem"},"plan_round":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Plan Round"},"skill_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skill Name"},"is_campaign":{"type":"boolean","title":"Is Campaign","default":false},"resume_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Mode"},"branding":{"anyOf":[{"$ref":"#/components/schemas/CandidateBranding"},{"type":"null"}]},"journey":{"allOf":[{"$ref":"#/components/schemas/CandidateJourneyInfo"}],"default":{"mode":"hiring","presenter_name":""}}},"type":"object","required":["candidate_first_name","job_title","company_name","status"],"title":"InterviewJoinInfo","description":"Public interview info for candidate (no sensitive data)"},"InterviewListItem":{"properties":{"id":{"type":"string","title":"Id","description":"Unique interview ID."},"status":{"type":"string","title":"Status","description":"'pending', 'processing', 'completed', 'failed'."},"transcript_status":{"type":"string","title":"Transcript Status","description":"Transcription sub-status."},"analysis_status":{"type":"string","title":"Analysis Status","description":"Analysis sub-status."},"input_mode":{"type":"string","title":"Input Mode","description":"'meeting' or 'recording'."},"created_at":{"type":"string","title":"Created At","description":"ISO 8601 timestamp."},"candidate":{"allOf":[{"$ref":"#/components/schemas/CandidateInfo"}],"description":"Candidate info."},"role":{"type":"string","title":"Role","description":"Job role."},"overall_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Overall Score","description":"Overall score 0-100 (if completed)."},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation","description":"Hiring recommendation (if completed)."},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata","description":"Custom metadata from submission."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if failed."}},"type":"object","required":["id","status","transcript_status","analysis_status","input_mode","created_at","candidate","role"],"title":"InterviewListItem","description":"Summary of an interview for list responses (no transcript, no evidence)."},"InterviewReadiness":{"properties":{"ready":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ready","description":"true: schedule with POST /interviews/schedule/brain. false: see `note`. null: the interview engine could not be consulted — retry GET /job-roles/{id}."},"min_duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Duration Minutes","description":"Shortest slot the rubric fits in, when ready: 45, 60 or 90, or 30 for an entry-level role."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","required":["ready"],"title":"InterviewReadiness","description":"Whether an AI-led (brain) interview can be scheduled on this role."},"InterviewRescheduleRequest":{"properties":{"scheduled_at":{"type":"string","format":"date-time","title":"Scheduled At"},"scheduled_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled Timezone"},"duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Minutes"},"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url"}},"type":"object","required":["scheduled_at"],"title":"InterviewRescheduleRequest","description":"In-place reschedule (ADR 0005, Phase 2).\n\nEditable set: date/time (+ its display timezone), duration, and an\noptional fresh meeting URL (reschedules often come with a newly-created\nZoom/Teams link). Interviewer swap is out of scope — that's closer to a\nnew interview. Only `scheduled_at` is required; the rest default to the\ncurrent values so the recruiter changes only what moved."},"InterviewRescheduleResponse":{"properties":{"interview_id":{"type":"string","title":"Interview Id"},"scheduled_at":{"type":"string","format":"date-time","title":"Scheduled At"},"scheduled_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled Timezone"},"duration_minutes":{"type":"integer","title":"Duration Minutes"},"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url"},"reschedule_count":{"type":"integer","title":"Reschedule Count"},"calendar_sync":{"type":"string","title":"Calendar Sync"}},"type":"object","required":["interview_id","scheduled_at","duration_minutes","reschedule_count","calendar_sync"],"title":"InterviewRescheduleResponse"},"InterviewResponse":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id"},"candidate_name":{"type":"string","title":"Candidate Name"},"candidate_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Email"},"candidate_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Phone"},"candidate_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Ref"},"role":{"type":"string","title":"Role"},"job_role_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Role Id"},"interviewer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interviewer Id"},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"scheduled_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled Timezone"},"duration_minutes":{"type":"integer","title":"Duration Minutes"},"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url"},"interviewer_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interviewer Email"},"meeting_link_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Link Status"},"failure_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Reason"},"calendar_health":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Calendar Health"},"interview_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Type"},"interview_stage_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Stage Id"},"stage_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Stage Order"},"stage_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Name"},"status":{"type":"string","title":"Status"},"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score"},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation"},"is_test_mode":{"type":"boolean","title":"Is Test Mode","default":false},"audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Url"},"audio_s3_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio S3 Key"},"audio_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Source"},"audio_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audio Size Bytes"},"audio_duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Audio Duration Seconds"},"transcript_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Status"},"analysis_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Status"},"analysis_results":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Analysis Results"},"recruiter_analysis":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Recruiter Analysis"},"candidate_analysis":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Candidate Analysis"},"generated_report_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Generated Report Types"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"actual_start_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Actual Start Time"},"actual_end_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Actual End Time"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"delivery_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivery Mode","default":"video_call"},"voice_call_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Call Id"},"voice_call_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Call Status"},"voice_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Provider"},"call_recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Call Recording Url"},"screening_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Screening Score"},"screening_recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screening Recommendation"},"screening_analysis":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Screening Analysis"},"screening_question_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Screening Question Ids"},"join_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Join Token"},"meeting_join_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Join Token"},"interviewer_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interviewer Type","default":"human"},"brain_analysis":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Brain Analysis"},"excluded":{"type":"boolean","title":"Excluded","default":false},"excluded_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Excluded Reason"},"excluded_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Excluded Notes"},"excluded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Excluded At"},"excluded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Excluded By"},"plan_round":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Plan Round"},"skill_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skill Id"},"speaker_roles_needs_confirmation":{"type":"boolean","title":"Speaker Roles Needs Confirmation","default":false},"speaker_roles_resolution":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Roles Resolution"},"speaker_roles_confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Speaker Roles Confirmed At"},"speaker_roles_confirmed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Roles Confirmed By"}},"type":"object","required":["id","organization_id","candidate_name","role","job_role_id","interviewer_id","scheduled_at","duration_minutes","meeting_url","interview_type","status","audio_url","audio_size_bytes","audio_duration_seconds","notes","actual_start_time","actual_end_time","created_at","updated_at","created_by"],"title":"InterviewResponse","description":"Interview response"},"InterviewStageResponse":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"job_position_id":{"type":"string","title":"Job Position Id"},"stage_name":{"type":"string","title":"Stage Name"},"stage_order":{"type":"integer","title":"Stage Order"},"stage_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Type"},"duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Minutes"},"is_bot_recorded":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Bot Recorded"},"is_required":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Required"},"competencies_to_evaluate":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Competencies To Evaluate"},"interviewer_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interviewer Instructions"},"candidate_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Instructions"},"preparation_materials":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preparation Materials"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"}},"type":"object","required":["id","organization_id","job_position_id","stage_name","stage_order"],"title":"InterviewStageResponse","description":"Full interview_stages row. Mirrors `SELECT *` from the old Next.js\nroute — every column the FE could read is declared so nothing is silently\ndropped when a column is added to the table."},"InterviewStageTemplate":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"},"template_name":{"type":"string","title":"Template Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"stages":{"title":"Stages"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","template_name","stages"],"title":"InterviewStageTemplate"},"InterviewStageTemplateListResponse":{"properties":{"templates":{"items":{"$ref":"#/components/schemas/InterviewStageTemplate"},"type":"array","title":"Templates"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["templates","total"],"title":"InterviewStageTemplateListResponse"},"InterviewStageUpdate":{"properties":{"stage_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Name"},"stage_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Stage Order"},"stage_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Type"},"duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Minutes"},"is_bot_recorded":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Bot Recorded"},"is_required":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Required"},"competencies_to_evaluate":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Competencies To Evaluate"}},"type":"object","title":"InterviewStageUpdate","description":"Partial-update payload. Field set mirrors the old route's whitelist\n(route.ts:59-67) EXACTLY — the schema has more columns\n(interviewer_instructions, candidate_instructions, preparation_materials,\nis_active) that were intentionally NOT editable here; do not add them\nwithout an explicit behavior-change decision."},"InterviewSubmitRequest":{"properties":{"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url","description":"URL of the meeting to join and record (Mode 1). BetterHire bot will join at the scheduled time, record the interview, then transcribe and analyze automatically. Currently supports Google Meet URLs.","examples":["https://meet.google.com/abc-defg-hij"]},"meeting_platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Platform","description":"Platform of the meeting. Required when using `meeting_url`. Supported: 'google_meet'. Zoom and Teams coming soon.","examples":["google_meet"]},"scheduled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled At","description":"ISO 8601 datetime when the meeting starts. Required when using `meeting_url`. The bot joins approximately 2 minutes after this time.","examples":["2026-03-28T14:00:00Z"]},"recording_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recording Url","description":"URL of an existing interview recording (Mode 2). Supports S3/GCS presigned URLs or any publicly accessible URL. Formats: mp3, wav, m4a, webm, ogg, mp4.","examples":["https://storage.example.com/interviews/recording-001.mp3"]},"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id","description":"BetterHire candidate ID from a previous submission. If provided, candidate_name and candidate_email are optional — we already know who they are. Returned in every POST response for reuse in subsequent interviews.","examples":["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]},"candidate_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Candidate Name","description":"Full name of the candidate. Required on first submission (when no candidate_id).","examples":["Priya Sharma"]},"candidate_email":{"anyOf":[{"type":"string","maxLength":254},{"type":"null"}],"title":"Candidate Email","description":"Candidate's email. Used for deduplication — same email = same candidate across interviews.","examples":["priya@example.com"]},"role":{"type":"string","maxLength":200,"title":"Role","description":"Job role the candidate is being interviewed for.","examples":["Senior Backend Engineer"]},"job_description":{"type":"string","maxLength":10000,"title":"Job Description","description":"Full job description text. BetterHire uses this to extract competencies and score the candidate against the actual role requirements."},"resume_text":{"anyOf":[{"type":"string","maxLength":50000},{"type":"null"}],"title":"Resume Text","description":"Candidate's resume as plain text. Recommended — allows the AI to cross-reference interview responses with claimed experience and skills. Without it, analysis is based solely on the transcript."},"interview_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Type","description":"Type of interview. Helps BetterHire choose the right analysis approach. One of: 'technical', 'behavioral', 'culture_fit', 'screening', 'general'. If not provided, defaults to 'general' (AI infers from transcript).","examples":["technical"]},"framework":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Framework","description":"Interview framework for analysis (optional, for power users). One of: 'BEI' (Behavioral Event Interview), 'CBI' (Competency-Based Interview), 'topgrading'. Overrides interview_type mapping if provided.","examples":["BEI"]},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata","description":"Arbitrary key-value metadata to attach to this interview. Returned in all subsequent responses. Use for your own internal IDs, tags, etc.","examples":[{"ats_candidate_id":"gh-12345","stage":"technical"}]}},"type":"object","required":["role","job_description"],"title":"InterviewSubmitRequest","description":"Submit an interview for AI analysis.\n\nTwo input modes — provide exactly one of `meeting_url` or `recording_url`:\n\n**Mode 1 (meeting link):** BetterHire joins the meeting, records, transcribes, and analyzes.\nRequires `scheduled_at`. Currently supports Google Meet.\n\n**Mode 2 (recording URL):** You provide an existing recording. BetterHire transcribes and analyzes."},"InterviewSubmitResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique interview ID. Use this to poll for status and retrieve results."},"candidate_id":{"type":"string","title":"Candidate Id","description":"Candidate ID. Store this — use it for subsequent interviews and to request debriefs."},"status":{"type":"string","title":"Status","description":"Initial status (always 'pending').","examples":["pending"]},"input_mode":{"type":"string","title":"Input Mode","description":"'meeting' if a bot will join the call, or 'recording' if processing an uploaded recording.","examples":["recording"]},"message":{"type":"string","title":"Message","description":"Human-readable confirmation message."}},"type":"object","required":["id","candidate_id","status","input_mode","message"],"title":"InterviewSubmitResponse","description":"Response after submitting an interview for analysis."},"InterviewSummary":{"properties":{"interview_id":{"type":"string","title":"Interview Id"},"role":{"type":"string","title":"Role"},"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score"},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["interview_id","role","status","created_at"],"title":"InterviewSummary"},"InterviewUpdateRequest":{"properties":{"candidate_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Name"},"candidate_email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Candidate Email"},"candidate_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Phone"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"job_role_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Job Role Id"},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Minutes"},"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url"},"interview_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Type"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","title":"InterviewUpdateRequest","description":"Update interview request"},"InvitationAcceptRequest":{"properties":{"invitation_token":{"type":"string","title":"Invitation Token"},"password":{"type":"string","title":"Password"},"full_name":{"type":"string","title":"Full Name"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"}},"type":"object","required":["invitation_token","password","full_name"],"title":"InvitationAcceptRequest","description":"Accept invitation request"},"InvitationCreateRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"role":{"type":"string","title":"Role"}},"type":"object","required":["email","role"],"title":"InvitationCreateRequest","description":"Send invitation request"},"InvitationListResponse":{"properties":{"invitations":{"items":{"$ref":"#/components/schemas/InvitationResponse"},"type":"array","title":"Invitations"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["invitations","total","limit","offset"],"title":"InvitationListResponse","description":"Invitation list response"},"InvitationResponse":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"},"invitation_token":{"type":"string","title":"Invitation Token"},"invited_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invited By"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"status":{"type":"string","title":"Status"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"},"accepted_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accepted By"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","organization_id","email","role","invitation_token","invited_by","expires_at","status","accepted_at","accepted_by","created_at"],"title":"InvitationResponse","description":"Invitation response"},"JDQualityRequest":{"properties":{"jd_text":{"type":"string","title":"Jd Text"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"}},"type":"object","required":["jd_text"],"title":"JDQualityRequest"},"JobRoleBenchmarkResponse":{"properties":{"n":{"type":"integer","title":"N"},"tier":{"type":"string","title":"Tier"},"overall_avg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overall Avg"},"competency_averages":{"items":{"$ref":"#/components/schemas/CompetencyAverage-Output"},"type":"array","title":"Competency Averages","default":[]},"skill_benchmarks":{"items":{"$ref":"#/components/schemas/SkillBenchmark"},"type":"array","title":"Skill Benchmarks","default":[]},"top_performer":{"anyOf":[{"$ref":"#/components/schemas/TopPerformer"},{"type":"null"}]},"top_quartile_threshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Top Quartile Threshold"},"voided_count":{"type":"integer","title":"Voided Count","default":0}},"type":"object","required":["n","tier"],"title":"JobRoleBenchmarkResponse","description":"Per-role hiring benchmark payload. Tier-gated cold-start lives in\nthe service layer (`core.benchmarks`); the UI branches on `tier`."},"JobRoleCandidateItem":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"source_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Details"},"fit_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fit Score"},"fit_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fit Source"},"stage":{"type":"string","title":"Stage"},"stage_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Name"},"interview_count":{"type":"integer","title":"Interview Count","default":0},"latest_interview":{"anyOf":[{"$ref":"#/components/schemas/LatestInterviewInfo"},{"type":"null"}]},"has_resume":{"type":"boolean","title":"Has Resume","default":false},"linkedin_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linkedin Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","stage","created_at"],"title":"JobRoleCandidateItem","description":"Individual candidate for a job role"},"JobRoleCandidatesResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/JobRoleCandidateItem"},"type":"array","title":"Candidates"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["candidates","total"],"title":"JobRoleCandidatesResponse","description":"Response with candidates for a job role"},"JobRoleInterviewItem":{"properties":{"id":{"type":"string","title":"Id"},"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id"},"candidate_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Name"},"candidate_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Email"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score"},"screening_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Screening Score"},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation"},"interview_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Type"},"interviewer_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interviewer Type"},"skill_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skill Id"},"plan_round":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Plan Round"}},"type":"object","required":["id","created_at"],"title":"JobRoleInterviewItem","description":"Individual interview for a job role"},"JobRoleInterviewsResponse":{"properties":{"interviews":{"items":{"$ref":"#/components/schemas/JobRoleInterviewItem"},"type":"array","title":"Interviews"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["interviews","total"],"title":"JobRoleInterviewsResponse","description":"Response with interviews for a job role"},"JobRoleSummary":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"experience_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Experience Level"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"competency_count":{"type":"integer","title":"Competency Count"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","title","competency_count","created_at"],"title":"JobRoleSummary"},"JobRoleUpdateRequest":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"location_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Type"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"employment_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employment Type"},"experience_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Experience Level"},"jd_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Text"},"job_description_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Description Url"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"use_manual_competencies":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Use Manual Competencies"},"custom_competencies":{"anyOf":[{"items":{"$ref":"#/components/schemas/CompetencyItem"},"type":"array"},{"type":"null"}],"title":"Custom Competencies"},"quality_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quality Score"},"hiring_company_display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Display"},"hiring_company_pronounce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Pronounce"},"hiring_company_visibility":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Visibility"},"min_years_experience":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Years Experience"},"max_years_experience":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Years Experience"},"assessment_plan":{"anyOf":[{"$ref":"#/components/schemas/AssessmentPlan"},{"type":"null"}]},"tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag"},"default_framework_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Framework Code"}},"type":"object","title":"JobRoleUpdateRequest","description":"Update job role request"},"LatestInterviewInfo":{"properties":{"id":{"type":"string","title":"Id"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score"},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation"},"screening_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Screening Score"},"interview_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Type"},"interviewer_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interviewer Type"}},"type":"object","required":["id"],"title":"LatestInterviewInfo","description":"Latest interview info for a candidate"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest","description":"User login credentials"},"LookupRequest":{"properties":{"email":{"type":"string","title":"Email","default":""}},"type":"object","title":"LookupRequest","description":"Pre-login SSO discovery — deliberately NOT EmailStr: the FE contract\nreturns specific 400 strings for malformed input, not a 422 list."},"ManualBotLaunchRequest":{"properties":{"meeting_url":{"type":"string","title":"Meeting Url"},"candidate_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Ref"}},"type":"object","required":["meeting_url"],"title":"ManualBotLaunchRequest"},"ManualTargetBody":{"properties":{"lane":{"type":"string","title":"Lane"},"company_name":{"type":"string","title":"Company Name"},"req_title":{"type":"string","title":"Req Title"},"jd_text":{"type":"string","title":"Jd Text"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"email_verified":{"type":"boolean","title":"Email Verified","default":false},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"req_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Req Url"},"company_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Domain"},"campaign_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Code"}},"type":"object","required":["lane","company_name","req_title","jd_text"],"title":"ManualTargetBody"},"MarkSentBody":{"properties":{"touch":{"type":"integer","title":"Touch"}},"type":"object","required":["touch"],"title":"MarkSentBody"},"MatchPreviewResponse":{"properties":{"extracted_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extracted Name"},"extracted_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extracted Email"},"resume_text_preview":{"type":"string","title":"Resume Text Preview"},"results":{"items":{"$ref":"#/components/schemas/MatchPreviewResultItem"},"type":"array","title":"Results"}},"type":"object","required":["extracted_name","extracted_email","resume_text_preview","results"],"title":"MatchPreviewResponse"},"MatchPreviewResultItem":{"properties":{"job_role_id":{"type":"string","title":"Job Role Id"},"role_title":{"type":"string","title":"Role Title"},"hiring_company_display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Display"},"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score"},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"requirements_met":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Requirements Met"},"requirements_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Requirements Total"},"strengths":{"items":{"type":"string"},"type":"array","title":"Strengths"},"concerns":{"items":{"type":"string"},"type":"array","title":"Concerns"},"suggested_questions":{"items":{"type":"string"},"type":"array","title":"Suggested Questions"},"cached":{"type":"boolean","title":"Cached"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["job_role_id","role_title","hiring_company_display","score","recommendation","summary","requirements_met","requirements_total","strengths","concerns","suggested_questions","cached","error"],"title":"MatchPreviewResultItem"},"MatchRequest":{"properties":{"role":{"type":"string","maxLength":200,"title":"Role","description":"Job title.","examples":["Senior Backend Engineer"]},"job_description":{"type":"string","maxLength":10000,"minLength":200,"title":"Job Description","description":"Full job description text."},"resumes":{"items":{"$ref":"#/components/schemas/ResumeInput"},"type":"array","maxItems":10,"minItems":1,"title":"Resumes","description":"1-10 resumes to match."}},"type":"object","required":["role","job_description","resumes"],"title":"MatchRequest"},"MatchResponse":{"properties":{"role":{"type":"string","title":"Role"},"candidates_matched":{"type":"integer","title":"Candidates Matched"},"ranking":{"items":{"$ref":"#/components/schemas/MatchResultItem"},"type":"array","title":"Ranking"}},"type":"object","required":["role","candidates_matched","ranking"],"title":"MatchResponse"},"MatchResultItem":{"properties":{"rank":{"type":"integer","title":"Rank"},"match_id":{"type":"string","title":"Match Id"},"candidate_name":{"type":"string","title":"Candidate Name"},"candidate_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Email"},"overall_score":{"type":"integer","title":"Overall Score"},"recommendation":{"type":"string","title":"Recommendation"},"summary":{"type":"string","title":"Summary"},"requirements_met":{"type":"integer","title":"Requirements Met"},"requirements_total":{"type":"integer","title":"Requirements Total"},"strengths":{"items":{"type":"string"},"type":"array","title":"Strengths"},"concerns":{"items":{"type":"string"},"type":"array","title":"Concerns"},"suggested_questions":{"items":{"$ref":"#/components/schemas/SuggestedQuestion"},"type":"array","title":"Suggested Questions"},"cached":{"type":"boolean","title":"Cached"}},"type":"object","required":["rank","match_id","candidate_name","candidate_email","overall_score","recommendation","summary","requirements_met","requirements_total","strengths","concerns","suggested_questions","cached"],"title":"MatchResultItem"},"MeetingJoinResult":{"properties":{"meeting_url":{"type":"string","title":"Meeting Url"},"bot":{"type":"string","title":"Bot"}},"type":"object","required":["meeting_url","bot"],"title":"MeetingJoinResult"},"OAuthUrlResponse":{"properties":{"authorization_url":{"type":"string","title":"Authorization Url"}},"type":"object","required":["authorization_url"],"title":"OAuthUrlResponse"},"Opening":{"properties":{"kind":{"type":"string","enum":["introduction","motivation"],"title":"Kind"},"question":{"type":"string","title":"Question"},"ref":{"$ref":"#/components/schemas/TurnRef"},"seconds_spoken":{"type":"integer","title":"Seconds Spoken"},"focus":{"type":"string","enum":["on_role","partly","wandered"],"title":"Focus"},"note":{"type":"string","title":"Note"}},"type":"object","required":["kind","question","ref","seconds_spoken","focus","note"],"title":"Opening","description":"The candidate's introduction or \"why this role\" answer: not a story,\nbut read for how long it ran and whether it led to the role."},"OutcomeBody":{"properties":{"outcome":{"type":"string","title":"Outcome"}},"type":"object","required":["outcome"],"title":"OutcomeBody"},"ParseResumeTextRequest":{"properties":{"resume_text":{"type":"string","title":"Resume Text"},"extract_contact":{"type":"boolean","title":"Extract Contact","default":true}},"type":"object","required":["resume_text"],"title":"ParseResumeTextRequest"},"ParsedResume":{"properties":{"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"linkedin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linkedin"},"github":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Github"},"portfolio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Portfolio"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"work_experience":{"items":{"$ref":"#/components/schemas/WorkExperience"},"type":"array","title":"Work Experience","default":[]},"total_years_experience":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Years Experience"},"education":{"items":{"$ref":"#/components/schemas/Education"},"type":"array","title":"Education","default":[]},"technical_skills":{"items":{"type":"string"},"type":"array","title":"Technical Skills","default":[]},"soft_skills":{"items":{"type":"string"},"type":"array","title":"Soft Skills","default":[]},"languages":{"items":{"type":"string"},"type":"array","title":"Languages","default":[]},"certifications":{"items":{"$ref":"#/components/schemas/Certification"},"type":"array","title":"Certifications","default":[]},"number_of_companies":{"type":"integer","title":"Number Of Companies","default":0},"average_tenure_months":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average Tenure Months"},"promotions_count":{"type":"integer","title":"Promotions Count","default":0},"career_progression":{"type":"string","title":"Career Progression","default":"Unknown"},"parsed_at":{"type":"string","title":"Parsed At","default":"2026-10-20T06:16:07.983003"},"parser_version":{"type":"string","title":"Parser Version","default":"1.0"}},"type":"object","title":"ParsedResume"},"PartnershipAcceptRequest":{"properties":{"invitation_token":{"type":"string","title":"Invitation Token","default":""},"password":{"type":"string","title":"Password","default":""},"full_name":{"type":"string","title":"Full Name","default":""},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"},"company_size":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Size"}},"type":"object","title":"PartnershipAcceptRequest","description":"Accept a partnership invitation (public endpoint).\n\nDefaults-not-required so the missing-fields 400 stays the exact string\nthe register page displays, not a Pydantic 422 list."},"PartnershipAction":{"properties":{"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action"},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"PartnershipAction"},"PartnershipDelete":{"properties":{"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","title":"PartnershipDelete"},"PartnershipInvitationCreateRequest":{"properties":{"invited_org_name":{"type":"string","title":"Invited Org Name"},"contact_email":{"type":"string","format":"email","title":"Contact Email"},"contact_person":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Person"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["invited_org_name","contact_email"],"title":"PartnershipInvitationCreateRequest","description":"Send partnership invitation request"},"PartnershipInvitationEmailRequest":{"properties":{"contact_email":{"type":"string","format":"email","title":"Contact Email"},"inviter_org_name":{"type":"string","title":"Inviter Org Name"},"inviter_org_type":{"type":"string","title":"Inviter Org Type"},"invited_org_name":{"type":"string","title":"Invited Org Name"},"contact_person":{"type":"string","title":"Contact Person"},"invitation_token":{"type":"string","title":"Invitation Token"},"expires_at":{"type":"string","title":"Expires At"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"inviter_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inviter Name"}},"type":"object","required":["contact_email","inviter_org_name","inviter_org_type","invited_org_name","contact_person","invitation_token","expires_at"],"title":"PartnershipInvitationEmailRequest","description":"Request model for sending partnership invitation email"},"PartnershipInvitationResponse":{"properties":{"id":{"type":"string","title":"Id"},"inviter_org_id":{"type":"string","title":"Inviter Org Id"},"inviter_org_name":{"type":"string","title":"Inviter Org Name"},"inviter_org_type":{"type":"string","title":"Inviter Org Type"},"invited_org_name":{"type":"string","title":"Invited Org Name"},"contact_email":{"type":"string","title":"Contact Email"},"contact_person":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Person"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"},"invitation_token":{"type":"string","title":"Invitation Token"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"status":{"type":"string","title":"Status"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"}},"type":"object","required":["id","inviter_org_id","inviter_org_name","inviter_org_type","invited_org_name","contact_email","contact_person","contact_phone","invitation_token","message","status","expires_at","created_at","accepted_at"],"title":"PartnershipInvitationResponse","description":"Partnership invitation response"},"PasswordResetConfirm":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["token","new_password"],"title":"PasswordResetConfirm","description":"Confirm password reset with token"},"PasswordResetRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"PasswordResetRequest","description":"Request password reset"},"PortalSessionRequest":{"properties":{"return_url":{"type":"string","title":"Return Url"}},"type":"object","required":["return_url"],"title":"PortalSessionRequest"},"PortalSessionResponse":{"properties":{"portal_url":{"type":"string","title":"Portal Url"}},"type":"object","required":["portal_url"],"title":"PortalSessionResponse"},"PostingUrlBody":{"properties":{"posting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Posting Url"}},"type":"object","title":"PostingUrlBody"},"PrepQuestion":{"properties":{"question":{"type":"string","title":"Question"},"rationale":{"type":"string","title":"Rationale"},"mapped_competency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mapped Competency"},"priority":{"type":"string","title":"Priority","default":"medium"},"source":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Source"}},"type":"object","required":["question","rationale"],"title":"PrepQuestion","description":"A single prep question"},"PrepQuestionsResponse":{"properties":{"interview_context":{"$ref":"#/components/schemas/InterviewContext"},"previous_rounds":{"$ref":"#/components/schemas/PreviousRoundsContext"},"questions":{"additionalProperties":{"items":{"$ref":"#/components/schemas/PrepQuestion"},"type":"array"},"type":"object","title":"Questions"},"flags":{"additionalProperties":{"items":{},"type":"array"},"type":"object","title":"Flags"},"competency_coverage":{"additionalProperties":{"$ref":"#/components/schemas/CompetencyCoverage"},"type":"object","title":"Competency Coverage"},"generated_at":{"type":"string","title":"Generated At"}},"type":"object","required":["interview_context","previous_rounds","questions","flags","competency_coverage","generated_at"],"title":"PrepQuestionsResponse","description":"Full response for prep questions"},"PreviousRoundSummary":{"properties":{"round":{"type":"integer","title":"Round"},"stage_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Name"},"topics_covered":{"items":{"type":"string"},"type":"array","title":"Topics Covered","default":[]},"flags_identified":{"items":{"type":"string"},"type":"array","title":"Flags Identified","default":[]},"unverified_claims":{"items":{"type":"string"},"type":"array","title":"Unverified Claims","default":[]}},"type":"object","required":["round"],"title":"PreviousRoundSummary","description":"Summary of a previous interview round"},"PreviousRoundsContext":{"properties":{"has_previous":{"type":"boolean","title":"Has Previous","default":false},"rounds_completed":{"type":"integer","title":"Rounds Completed","default":0},"summary":{"items":{"$ref":"#/components/schemas/PreviousRoundSummary"},"type":"array","title":"Summary","default":[]},"already_covered":{"items":{"type":"string"},"type":"array","title":"Already Covered","default":[]},"follow_up_on":{"items":{"$ref":"#/components/schemas/FollowUpItem"},"type":"array","title":"Follow Up On","default":[]}},"type":"object","title":"PreviousRoundsContext","description":"Context from previous interview rounds"},"PricingResponse":{"properties":{"cap_cents":{"type":"integer","title":"Cap Cents"},"action_prices_cents":{"additionalProperties":{"type":"integer"},"type":"object","title":"Action Prices Cents"},"signup_grant_cents":{"type":"integer","title":"Signup Grant Cents"},"topup_packs_cents":{"items":{"type":"integer"},"type":"array","title":"Topup Packs Cents"},"min_topup_cents":{"type":"integer","title":"Min Topup Cents"}},"type":"object","required":["cap_cents","action_prices_cents","signup_grant_cents","topup_packs_cents","min_topup_cents"],"title":"PricingResponse","description":"Public cap-pricing description. No auth required.\n\nUsed by GET /api/billing/pricing for marketing pages and the\npricing-drift CI check."},"ProcessingStatusResponse":{"properties":{"transcript_status":{"type":"string","title":"Transcript Status"},"analysis_status":{"type":"string","title":"Analysis Status"},"transcript_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Job Id"},"analysis_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Job Id"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["transcript_status","analysis_status","transcript_job_id","analysis_job_id","error_message"],"title":"ProcessingStatusResponse","description":"Current processing status"},"PublicContactRequest":{"properties":{"email":{"type":"string","maxLength":320,"title":"Email"},"message":{"type":"string","maxLength":2000,"title":"Message"},"source":{"type":"string","maxLength":40,"title":"Source","default":"marketing_footer"},"company_website":{"type":"string","title":"Company Website","default":""}},"type":"object","required":["email","message"],"title":"PublicContactRequest"},"QuestionCreate":{"properties":{"question_text":{"type":"string","maxLength":2000,"minLength":10,"title":"Question Text"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"career_phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Career Phase"},"probing_questions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Probing Questions","default":[]},"what_to_listen_for":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"What To Listen For","default":[]},"framework_code":{"type":"string","title":"Framework Code"},"competency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Competency Id"}},"type":"object","required":["question_text","framework_code"],"title":"QuestionCreate"},"QuestionListResponse":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/QuestionResponse"},"type":"array","title":"Questions"},"total":{"type":"integer","title":"Total"},"categories":{"items":{"type":"string"},"type":"array","title":"Categories"}},"type":"object","required":["questions","total","categories"],"title":"QuestionListResponse"},"QuestionResponse":{"properties":{"question_text":{"type":"string","maxLength":2000,"minLength":10,"title":"Question Text"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"career_phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Career Phase"},"probing_questions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Probing Questions","default":[]},"what_to_listen_for":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"What To Listen For","default":[]},"id":{"type":"string","title":"Id"},"framework_code":{"type":"string","title":"Framework Code"},"competency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Competency Id"},"competency_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Competency Name"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"},"is_system_default":{"type":"boolean","title":"Is System Default"},"source":{"type":"string","title":"Source"},"is_active":{"type":"boolean","title":"Is Active"},"usage_count":{"type":"integer","title":"Usage Count"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["question_text","id","framework_code","is_system_default","source","is_active","usage_count"],"title":"QuestionResponse"},"QuestionUpdate":{"properties":{"question_text":{"anyOf":[{"type":"string","maxLength":2000,"minLength":10},{"type":"null"}],"title":"Question Text"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"career_phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Career Phase"},"probing_questions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Probing Questions"},"what_to_listen_for":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"What To Listen For"},"competency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Competency Id"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"QuestionUpdate"},"RecommendSkillsRequest":{"properties":{"jd_text":{"type":"string","title":"Jd Text"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"}},"type":"object","required":["jd_text"],"title":"RecommendSkillsRequest","description":"Inline-JD variant of recommend-skills (mirrors ExtractCompetenciesRequest):\nthe role create/edit forms hold *unsaved* JD text — the create page has no\nrole row at all, and on edit the in-form JD is fresher than the DB row —\nso they can't use the per-role route."},"RefreshTokenRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["refresh_token"],"title":"RefreshTokenRequest","description":"Refresh token to get new access token"},"RegisterRequest":{"properties":{"organization_name":{"type":"string","title":"Organization Name"},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"},"company_size":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Size"},"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"},"full_name":{"type":"string","title":"Full Name"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"signup_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signup Source"}},"type":"object","required":["organization_name","email","password","full_name"],"title":"RegisterRequest","description":"Organization and admin user registration"},"ResendVerificationRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"ResendVerificationRequest","description":"Re-send the signup verification link to an unverified address."},"ResolveCandidateRequest":{"properties":{"email":{"type":"string","title":"Email"},"name":{"type":"string","title":"Name","default":""}},"type":"object","required":["email"],"title":"ResolveCandidateRequest"},"ResolveEmailsRequest":{"properties":{"emails":{"items":{"type":"string"},"type":"array","minItems":2,"title":"Emails","description":"Candidate emails to resolve to candidate_ids (min 2)."}},"type":"object","required":["emails"],"title":"ResolveEmailsRequest"},"ResumeInput":{"properties":{"resume_text":{"type":"string","maxLength":50000,"minLength":50,"title":"Resume Text","description":"Resume as plain text."},"candidate_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Candidate Name","description":"Optional — auto-extracted from resume if not provided."},"candidate_email":{"anyOf":[{"type":"string","maxLength":254},{"type":"null"}],"title":"Candidate Email","description":"Optional — auto-extracted from resume if not provided."}},"type":"object","required":["resume_text"],"title":"ResumeInput"},"RiskAssessment":{"properties":{"level":{"type":"string","title":"Level","description":"Overall risk level.","examples":["low"]},"risks":{"items":{"$ref":"#/components/schemas/RiskItem"},"type":"array","title":"Risks"},"description":{"type":"string","title":"Description","default":""}},"type":"object","required":["level"],"title":"RiskAssessment"},"RiskEntry":{"properties":{"candidate_id":{"type":"string","title":"Candidate Id"},"name":{"type":"string","title":"Name"},"risk_level":{"type":"string","title":"Risk Level","description":"low | medium | high | unknown"},"confidence":{"type":"string","title":"Confidence"}},"type":"object","required":["candidate_id","name","risk_level","confidence"],"title":"RiskEntry"},"RiskItem":{"properties":{"title":{"type":"string","title":"Title"},"severity":{"type":"string","title":"Severity","description":"'high', 'medium', or 'low'."}},"type":"object","required":["title","severity"],"title":"RiskItem"},"RoundOutcome":{"properties":{"round":{"type":"string","title":"Round","description":"Interview round name.","examples":["Phone Screening"]},"score":{"type":"integer","title":"Score","description":"Score 0-100.","examples":[81]},"recommendation":{"type":"string","title":"Recommendation","description":"strong_hire | hire | maybe | no_hire"},"date":{"type":"string","title":"Date","description":"Date of interview.","default":"","examples":["Jan 23"]},"interview_id":{"type":"string","title":"Interview Id","description":"Interview ID for this round.","default":""}},"type":"object","required":["round","score","recommendation"],"title":"RoundOutcome"},"RoundSummary":{"properties":{"round":{"type":"string","title":"Round"},"score":{"type":"integer","title":"Score"},"recommendation":{"type":"string","title":"Recommendation"},"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"}},"type":"object","required":["round","score","recommendation"],"title":"RoundSummary"},"RubricCompetency":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"}},"type":"object","required":["name","description"],"title":"RubricCompetency"},"SSOConfigCreateRequest":{"properties":{"provider_type":{"type":"string","title":"Provider Type","default":""},"provider_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Name"},"client_id":{"type":"string","title":"Client Id","default":""},"client_secret":{"type":"string","title":"Client Secret","default":""},"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"},"allowed_domains":{"items":{"type":"string"},"type":"array","title":"Allowed Domains","default":[]},"jit_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Jit Enabled"},"jit_default_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jit Default Role"},"jit_default_user_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jit Default User Type"}},"type":"object","title":"SSOConfigCreateRequest"},"SSOConfigUpdateRequest":{"properties":{"provider_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret"},"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"},"allowed_domains":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Domains"},"jit_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Jit Enabled"},"jit_default_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jit Default Role"},"jit_default_user_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jit Default User Type"},"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled"},"require_sso_only":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Require Sso Only"}},"type":"object","title":"SSOConfigUpdateRequest"},"SavePipelineRequest":{"properties":{"job_role_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Role Id"},"job_role_title":{"type":"string","title":"Job Role Title"},"seniority":{"type":"string","title":"Seniority","default":"senior"},"company_type":{"type":"string","title":"Company Type","default":"tech"},"jd_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Text"},"jd_pdf_base64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Pdf Base64"},"jd_competencies":{"items":{"type":"string"},"type":"array","title":"Jd Competencies","default":[]},"resume_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Text"},"resume_pdf_base64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Pdf Base64"},"resume_candidate_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Candidate Name"},"resume_quality":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume Quality"},"resume_years_exp":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Resume Years Exp"},"resume_key_skills":{"items":{"type":"string"},"type":"array","title":"Resume Key Skills","default":[]},"transcript_json":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Transcript Json"},"transcript_framework":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Framework"},"audio_duration_minutes":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Audio Duration Minutes"},"interview_mode":{"type":"string","title":"Interview Mode","default":"standard"},"screening_question_ids":{"items":{"type":"string"},"type":"array","title":"Screening Question Ids","default":[]},"phone_screen_transcript_json":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Phone Screen Transcript Json"}},"type":"object","required":["job_role_title"],"title":"SavePipelineRequest"},"ScheduledMeetingResponse":{"properties":{"id":{"type":"string","title":"Id"},"meeting_title":{"type":"string","title":"Meeting Title"},"meeting_platform":{"type":"string","title":"Meeting Platform"},"meeting_url":{"type":"string","title":"Meeting Url"},"scheduled_start":{"type":"string","title":"Scheduled Start"},"scheduled_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled End"},"candidate_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Ref"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"status":{"type":"string","title":"Status"},"calendar_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Calendar Email"}},"type":"object","required":["id","meeting_title","meeting_platform","meeting_url","scheduled_start","scheduled_end","candidate_ref","role","status","calendar_email"],"title":"ScheduledMeetingResponse"},"Scorecard":{"properties":{"overall_score":{"type":"integer","title":"Overall Score","description":"Overall candidate score 0-100.","examples":[78]},"recommendation":{"type":"string","title":"Recommendation","description":"Hiring recommendation: 'strong_hire', 'hire', 'maybe', 'no_hire'.","examples":["hire"]},"summary":{"allOf":[{"$ref":"#/components/schemas/ScorecardSummary"}],"description":"Executive summary (BLUF)."},"competencies":{"items":{"$ref":"#/components/schemas/CompetencyScore"},"type":"array","title":"Competencies","description":"Per-competency scores with evidence and concerns."},"action_plan":{"items":{"$ref":"#/components/schemas/ActionPlanItem"},"type":"array","title":"Action Plan","description":"Prioritized next steps for the hiring process."},"communication":{"anyOf":[{"$ref":"#/components/schemas/CommunicationAnalysis"},{"type":"null"}],"description":"Speech and communication quality metrics."},"not_assessed":{"items":{"type":"string"},"type":"array","title":"Not Assessed","description":"Names of competencies the interview did not reach, so they carry no score and are absent from `competencies`. Empty when everything was scored.","examples":[["Talent Development","Strategic Communication"]]},"coverage_summary":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Coverage Summary","description":"Counts by coverage level across the role's competencies: total, covered, partial, not_reached, resume_only, asserted_only, unscored_count. Null on analyses that predate coverage tracking."},"score_confidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Score Confidence","description":"How much of the rubric the score rests on: 'high', 'medium' or 'low'.","examples":["medium"]},"calibrated_for_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Calibrated For Level","description":"The experience level the scores were calibrated to, when one was applied: 'entry' for intern / graduate / first-job roles, where projects and coursework count as evidence and business outcomes are not expected. Null means the standard bar.","examples":["entry"]},"transcript_quality":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Quality","description":"Transcript-quality gate label: 'full', 'partial' or 'invalid'.","examples":["full"]},"band":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Band","description":"Overall band on the canonical ladder: 'Excellent', 'Strong', 'Good', 'Moderate' or 'Needs Work'. Null on analyses that predate banding.","examples":["Good"]}},"type":"object","required":["overall_score","recommendation","summary"],"title":"Scorecard","description":"AI-generated interview analysis scorecard. The core product output."},"ScorecardSummary":{"properties":{"strengths":{"items":{"type":"string"},"type":"array","title":"Strengths","description":"Key strengths identified."},"concerns":{"items":{"type":"string"},"type":"array","title":"Concerns","description":"Key areas of concern."},"next_steps":{"items":{"type":"string"},"type":"array","title":"Next Steps","description":"Recommended next steps."}},"type":"object","title":"ScorecardSummary","description":"Bottom Line Up Front — executive summary of the interview."},"ScreenStartBody":{"properties":{"candidate_name":{"type":"string","title":"Candidate Name"},"candidate_email":{"type":"string","title":"Candidate Email"}},"type":"object","required":["candidate_name","candidate_email"],"title":"ScreenStartBody"},"ScreeningQuestionCreate":{"properties":{"question_text":{"type":"string","maxLength":500,"minLength":1,"title":"Question Text"},"category":{"anyOf":[{"type":"string","pattern":"^(logistics|experience|motivation|culture|custom)$"},{"type":"null"}],"title":"Category"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["question_text"],"title":"ScreeningQuestionCreate"},"ScreeningQuestionListResponse":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/ScreeningQuestionResponse"},"type":"array","title":"Questions"},"total":{"type":"integer","title":"Total"},"categories":{"items":{"type":"string"},"type":"array","title":"Categories"}},"type":"object","required":["questions","total","categories"],"title":"ScreeningQuestionListResponse"},"ScreeningQuestionResponse":{"properties":{"question_text":{"type":"string","maxLength":500,"minLength":1,"title":"Question Text"},"category":{"anyOf":[{"type":"string","pattern":"^(logistics|experience|motivation|culture|custom)$"},{"type":"null"}],"title":"Category"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"id":{"type":"string","title":"Id"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"},"is_system_default":{"type":"boolean","title":"Is System Default"},"is_active":{"type":"boolean","title":"Is Active"},"display_order":{"type":"integer","title":"Display Order"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["question_text","id","is_system_default","is_active","display_order"],"title":"ScreeningQuestionResponse"},"ScreeningQuestionUpdate":{"properties":{"question_text":{"anyOf":[{"type":"string","maxLength":500,"minLength":1},{"type":"null"}],"title":"Question Text"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"display_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Display Order"}},"type":"object","title":"ScreeningQuestionUpdate"},"ScreeningRequest":{"properties":{"candidate_name":{"type":"string","maxLength":200,"title":"Candidate Name","description":"Full name of the candidate.","examples":["Raj Patel"]},"candidate_email":{"type":"string","maxLength":254,"title":"Candidate Email","description":"Candidate's email address. Used for deduplication and video mode invites.","examples":["raj.patel@example.com"]},"candidate_phone":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Candidate Phone","description":"Candidate's phone number in E.164 format. Required for phone mode.","examples":["+919876543210"]},"role":{"type":"string","maxLength":200,"title":"Role","description":"Job title the candidate is being screened for.","examples":["Senior Backend Engineer"]},"job_role_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Role Id","description":"Exact role id to screen against. Wins over `role`. Pass this when your org has multiple roles sharing a title — otherwise an ambiguous `role` returns 409 listing the candidate role ids.","examples":["9ebd749c-3462-437a-8a17-81395b772568"]},"job_description":{"type":"string","maxLength":10000,"minLength":200,"title":"Job Description","description":"Full job description text. The AI bot uses this to tailor screening questions."},"mode":{"type":"string","title":"Mode","description":"Screening mode: 'phone' (AI calls candidate) or 'video' (candidate joins Daily.co room via email link).","default":"phone"},"resume_text":{"anyOf":[{"type":"string","maxLength":50000},{"type":"null"}],"title":"Resume Text","description":"Candidate's resume as plain text. Improves question relevance and gap analysis."},"company_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Company Name","description":"Company name for the AI bot's introduction. Defaults to organization name."},"screening_questions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Screening Questions","description":"Custom screening questions to include in the interview.","examples":[["Why are you looking to leave your current role?","What's your expected compensation?"]]},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata","description":"Custom metadata to attach to this screening interview."},"return_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Return Url","description":"Video mode only, and only for organisations whose candidate journey is integrator-owned (Settings → Appearance → Candidate journey). When the call ends the candidate's browser is sent here with `?interview_id=` appended, instead of BetterHire's thank-you screen. Must be https. Overrides the organisation's default return address for this screening."},"send_invite":{"type":"boolean","title":"Send Invite","description":"Video mode only. Set false to skip BetterHire's invitation email and deliver `join_url` to the candidate yourself — for integrations that own the candidate conversation. The response reports `invite_sent`. Rejected in phone mode: the call is the contact, and there is nothing to withhold.","default":true}},"type":"object","required":["candidate_name","candidate_email","role","job_description"],"title":"ScreeningRequest","description":"Trigger an AI screening interview for a candidate."},"ScreeningResponse":{"properties":{"interview_id":{"type":"string","title":"Interview Id","description":"Interview ID. Use to poll status via GET /interviews/{id}."},"candidate_id":{"type":"string","title":"Candidate Id","description":"Candidate ID for future submissions."},"status":{"type":"string","title":"Status","description":"'initiated' (phone) or 'scheduled' (video)."},"mode":{"type":"string","title":"Mode","description":"'phone' or 'video'."},"join_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Join Url","description":"For video mode: URL the candidate uses to join the screening room."},"invite_sent":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Invite Sent","description":"Video mode: whether BetterHire emailed the candidate the join link. False when `send_invite` was false, when the email could not be sent, and always in the sandbox — deliver `join_url` yourself in those cases. Null in phone mode (the call is the contact)."},"message":{"type":"string","title":"Message","description":"Human-readable status message."}},"type":"object","required":["interview_id","candidate_id","status","mode","message"],"title":"ScreeningResponse","description":"Response after triggering a screening interview."},"SendVerificationEmailRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"full_name":{"type":"string","title":"Full Name"},"verification_token":{"type":"string","title":"Verification Token"},"signup_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signup Source"}},"type":"object","required":["email","full_name","verification_token"],"title":"SendVerificationEmailRequest","description":"Internal request to send verification email"},"SentBody":{"properties":{"touch":{"type":"integer","title":"Touch"}},"type":"object","required":["touch"],"title":"SentBody"},"SetCallIdRequest":{"properties":{"call_id":{"type":"string","title":"Call Id"}},"type":"object","required":["call_id"],"title":"SetCallIdRequest","description":"Set external call ID for voice interview (Vapi Web SDK)"},"SetStatusRequest":{"properties":{"status":{"type":"string","title":"Status","description":"New status. Must be one of: answered, asked, open, skipped"},"asked_in_interview_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asked In Interview Id"},"answer_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer Summary"}},"type":"object","required":["status"],"title":"SetStatusRequest"},"SettingsUpdate":{"properties":{"settings":{"anyOf":[{},{"type":"null"}],"title":"Settings"}},"type":"object","title":"SettingsUpdate"},"Skill":{"properties":{"skill_id":{"type":"string","title":"Skill Id","description":"Pass as `skill_id` when scheduling."},"display_name":{"type":"string","title":"Display Name"},"also_known_as":{"items":{"type":"string"},"type":"array","title":"Also Known As","description":"Industry titles for the SAME assessment (e.g. 'DevOps' for site_reliability). Match on these when mapping your own role titles; never schedule two of them for one candidate."},"rubric":{"items":{"$ref":"#/components/schemas/SkillRubricDimension"},"type":"array","title":"Rubric","description":"What the round is scored on, in catalog order."},"stage_count":{"type":"integer","title":"Stage Count"},"requires_code_editor":{"type":"boolean","title":"Requires Code Editor","description":"The round opens a code editor: the candidate needs a desktop browser, not a phone."},"suitable_for_entry":{"type":"boolean","title":"Suitable For Entry","description":"Offered to entry-level roles (interns, graduates, first jobs). An entry role's `inferred.skill_ids` only suggests these. Any skill booked on an entry role runs calibrated to entry level; an unmarked one returns a warning.","default":false}},"type":"object","required":["skill_id","display_name","rubric","stage_count","requires_code_editor"],"title":"Skill"},"SkillBenchmark":{"properties":{"skill_id":{"type":"string","title":"Skill Id"},"label":{"type":"string","title":"Label"},"n":{"type":"integer","title":"N"},"criteria":{"items":{"$ref":"#/components/schemas/CompetencyAverage-Output"},"type":"array","title":"Criteria","default":[]}},"type":"object","required":["skill_id","label","n"],"title":"SkillBenchmark","description":"Per-skill aggregate: a skill round's rubric criteria averaged across\nthat skill's completed runs for the role. Kept separate from\n`competency_averages` — skill rubric criteria are not role\ncompetencies (and system_design's criteria even share names with\nthem)."},"SkillListResponse":{"properties":{"skills":{"items":{"$ref":"#/components/schemas/Skill"},"type":"array","title":"Skills"}},"type":"object","required":["skills"],"title":"SkillListResponse"},"SkillRubricDimension":{"properties":{"name":{"type":"string","title":"Name"},"weight":{"type":"integer","title":"Weight","description":"3 = a core dimension that carries the verdict; 2 = supporting."}},"type":"object","required":["name","weight"],"title":"SkillRubricDimension"},"StageBody":{"properties":{"stage":{"type":"string","title":"Stage"}},"type":"object","required":["stage"],"title":"StageBody"},"StageCompetencyConfig":{"properties":{"competency_ids":{"items":{"type":"string"},"type":"array","title":"Competency Ids","default":[]},"custom_competencies":{"items":{"$ref":"#/components/schemas/CompetencyBase"},"type":"array","title":"Custom Competencies","default":[]}},"type":"object","title":"StageCompetencyConfig","description":"Configuration for stage-level competencies"},"StartConsentRequest":{"properties":{"recording_consent":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Recording Consent"}},"type":"object","title":"StartConsentRequest","description":"Recording-consent decision captured on the candidate welcome screen\n(ADR-0034 P5): recording_consent = {audio: bool, video: bool, agreed_at: ISO}.\nOptional so the existing no-body /start callers keep working."},"StoreCodeRequest":{"properties":{"code":{"type":"string","title":"Code"},"client_id":{"type":"string","title":"Client Id"},"user_id":{"type":"string","title":"User Id"},"organization_id":{"type":"string","title":"Organization Id"},"redirect_uri":{"type":"string","title":"Redirect Uri"},"code_challenge":{"type":"string","title":"Code Challenge"},"scope":{"type":"string","title":"Scope","default":"mcp:read mcp:write"},"expires_at":{"type":"string","title":"Expires At"}},"type":"object","required":["code","client_id","user_id","organization_id","redirect_uri","code_challenge","expires_at"],"title":"StoreCodeRequest"},"Story":{"properties":{"question":{"type":"string","title":"Question"},"competency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Competency"},"ref":{"$ref":"#/components/schemas/TurnRef"},"present":{"items":{"type":"string"},"type":"array","title":"Present"},"when_asked":{"items":{"type":"string"},"type":"array","title":"When Asked"},"missing":{"items":{"type":"string"},"type":"array","title":"Missing"},"note":{"type":"string","title":"Note"}},"type":"object","required":["question","ref","present","when_asked","missing","note"],"title":"Story"},"StrengthConcern":{"properties":{"text":{"type":"string","title":"Text"},"frequency":{"type":"integer","title":"Frequency"}},"type":"object","required":["text","frequency"],"title":"StrengthConcern"},"Structure":{"properties":{"status":{"type":"string","enum":["ready","not_applicable","unavailable"],"title":"Status"},"reason":{"anyOf":[{"type":"string","enum":["skill_round","generation_failed"]},{"type":"null"}],"title":"Reason"},"style":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Style"},"parts":{"items":{"type":"string"},"type":"array","title":"Parts"},"stories":{"items":{"$ref":"#/components/schemas/Story"},"type":"array","title":"Stories"},"pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pattern"},"openings":{"items":{"$ref":"#/components/schemas/Opening"},"type":"array","title":"Openings"},"transcript_fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Fingerprint"}},"type":"object","required":["status"],"title":"Structure","description":"The story list: each story's framework parts, told unprompted\n(`present`), only after the interviewer asked (`when_asked`), or never\n(`missing`)."},"SuggestedQuestion":{"properties":{"question":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Question"},"rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rationale"}},"type":"object","title":"SuggestedQuestion","description":"One suggested Round 1 question for a matched candidate.\n\nKeys mirror the `interview_questions` schema our own match prompt asks\nfor (core/fit_scoring.py). Both fields are Optional because the payload\nis LLM-generated: a missing key previously flowed through as a partial\ndict, and typing it must not turn that into a 500.\n\nTyped rather than left as `dict` because /match is CORE — ADR-003 §3\nforbids promoting an endpoint whose response contains an untyped\ncontainer, since neither review nor the contract snapshot can tell\nwhether a change to it was breaking."},"SupportRequest":{"properties":{"type":{"type":"string","title":"Type","description":"question | bug | feature"},"message":{"type":"string","maxLength":4000,"title":"Message"}},"type":"object","required":["type","message"],"title":"SupportRequest"},"TargetContactBody":{"properties":{"contact_email":{"type":"string","title":"Contact Email"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Title"},"email_verified":{"type":"boolean","title":"Email Verified","default":false}},"type":"object","required":["contact_email"],"title":"TargetContactBody"},"TeamMemberListResponse":{"properties":{"members":{"items":{"$ref":"#/components/schemas/TeamMemberResponse"},"type":"array","title":"Members"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["members","total","limit","offset"],"title":"TeamMemberListResponse","description":"Team member list response"},"TeamMemberResponse":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"role":{"type":"string","title":"Role"},"status":{"type":"string","title":"Status"},"email_verified":{"type":"boolean","title":"Email Verified"},"last_login_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login At"},"login_count":{"type":"integer","title":"Login Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","email","full_name","job_title","phone","role","status","email_verified","last_login_at","login_count","created_at","updated_at"],"title":"TeamMemberResponse","description":"Team member response"},"TeamMemberRoleUpdate":{"properties":{"role":{"type":"string","title":"Role"}},"type":"object","required":["role"],"title":"TeamMemberRoleUpdate","description":"Body for the role-only PATCH (mirrors the Next.js\n/api/team/members/[id] PATCH)."},"TeamMemberUpdateRequest":{"properties":{"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","title":"TeamMemberUpdateRequest","description":"Update team member request"},"TemplateBody":{"properties":{"template":{"type":"string","title":"Template"}},"type":"object","required":["template"],"title":"TemplateBody"},"TestModeRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"TestModeRequest"},"TopPerformer":{"properties":{"candidate_name":{"type":"string","title":"Candidate Name"},"score":{"type":"number","title":"Score"},"interview_id":{"type":"string","title":"Interview Id"}},"type":"object","required":["candidate_name","score","interview_id"],"title":"TopPerformer","description":"Highest-scoring interview for a role — used to anchor the benchmark."},"TopUpRequest":{"properties":{"amount_cents":{"type":"integer","title":"Amount Cents"},"success_url":{"type":"string","title":"Success Url"},"cancel_url":{"type":"string","title":"Cancel Url"}},"type":"object","required":["amount_cents","success_url","cancel_url"],"title":"TopUpRequest"},"TopUpResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"checkout_url":{"type":"string","title":"Checkout Url"}},"type":"object","required":["session_id","checkout_url"],"title":"TopUpResponse"},"TransactionItem":{"properties":{"id":{"type":"string","title":"Id"},"delta_cents":{"type":"integer","title":"Delta Cents"},"balance_after_cents":{"type":"integer","title":"Balance After Cents"},"type":{"type":"string","title":"Type"},"sku":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sku"},"interview_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Id"},"stripe_payment_intent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Payment Intent Id"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","delta_cents","balance_after_cents","type","created_at"],"title":"TransactionItem"},"TransactionListResponse":{"properties":{"transactions":{"items":{"$ref":"#/components/schemas/TransactionItem"},"type":"array","title":"Transactions"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["transactions"],"title":"TransactionListResponse"},"TranscriptQualitySummary":{"properties":{"label":{"type":"string","title":"Label","description":"'full' — scored. 'partial' or 'invalid' — the transcript-quality gate skipped scoring (the candidate said too little), so `scorecard` is null.","examples":["invalid"]},"candidate_word_count":{"type":"integer","title":"Candidate Word Count","description":"Words the candidate spoke, as the gate measured them."},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons","description":"The gate's reasons, when it recorded any."}},"type":"object","required":["label","candidate_word_count"],"title":"TranscriptQualitySummary","description":"Why a completed interview has, or has not, a scorecard (0003 §6c)."},"TranscriptionRequest":{"properties":{"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","default":"assemblyai"},"enable_diarization":{"type":"boolean","title":"Enable Diarization","default":true},"num_speakers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Speakers","default":2}},"type":"object","title":"TranscriptionRequest","description":"Request to transcribe interview audio"},"TranscriptionResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id"},"message":{"type":"string","title":"Message"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","required":["success","job_id","message","status"],"title":"TranscriptionResponse","description":"Transcription job response"},"TrendData":{"properties":{"start":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start"},"end":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"End"},"values":{"items":{"type":"integer"},"type":"array","title":"Values"}},"type":"object","title":"TrendData"},"TrustContactBody":{"properties":{"trusted":{"type":"boolean","title":"Trusted","default":true}},"type":"object","title":"TrustContactBody"},"TurnRef":{"properties":{"first_turn":{"type":"integer","title":"First Turn"},"last_turn":{"type":"integer","title":"Last Turn"},"start_seconds":{"type":"number","title":"Start Seconds"},"end_seconds":{"type":"number","title":"End Seconds"}},"type":"object","required":["first_turn","last_turn","start_seconds","end_seconds"],"title":"TurnRef","description":"A span of `GET /interviews/{id}` → `transcript.segments[]`."},"UploadStatusResponse":{"properties":{"status":{"type":"string","title":"Status"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"jd_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Filename"},"jd_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Preview"},"resume_count":{"type":"integer","title":"Resume Count"},"resume_filenames":{"items":{"type":"string"},"type":"array","title":"Resume Filenames"}},"type":"object","required":["status","role","jd_filename","jd_preview","resume_count","resume_filenames"],"title":"UploadStatusResponse"},"UserResponse":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"job_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Title"},"role":{"type":"string","title":"Role"},"status":{"type":"string","title":"Status"},"organization_id":{"type":"string","title":"Organization Id"},"organization_name":{"type":"string","title":"Organization Name"},"organization_status":{"type":"string","title":"Organization Status"},"organization_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Type"},"is_internal":{"type":"boolean","title":"Is Internal","default":false}},"type":"object","required":["id","email","full_name","job_title","role","status","organization_id","organization_name","organization_status"],"title":"UserResponse","description":"Current user information"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VoidInterviewRequest":{"properties":{"reason":{"type":"string","title":"Reason"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["reason"],"title":"VoidInterviewRequest","description":"Payload for marking an interview as invalid (data hygiene). Reason\nis closed-list to keep admin reporting consistent — see\n`core.interview_void.VALID_VOID_REASONS`. Notes are optional and\nfree-text for context the recruiter wants to leave for themselves\nor a teammate."},"VoidInterviewResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"interview_id":{"type":"string","title":"Interview Id"},"excluded":{"type":"boolean","title":"Excluded"},"excluded_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Excluded Reason"},"excluded_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Excluded Notes"},"excluded_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Excluded At"},"excluded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Excluded By"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["success","interview_id","excluded"],"title":"VoidInterviewResponse","description":"Result of a void/unvoid action — fits the post-write shape used elsewhere."},"WebhookCreateRequest":{"properties":{"url":{"type":"string","title":"Url","description":"HTTPS URL to receive webhook POST requests. Must be publicly accessible.","examples":["https://yourapp.com/webhooks/betterhire"]},"events":{"items":{"type":"string"},"type":"array","title":"Events","description":"Event types to subscribe to. Available: interview.transcribed, interview.analyzed, interview.failed, candidate_feedback.completed","default":["interview.analyzed","interview.failed"],"examples":[["interview.analyzed","interview.failed"]]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional description for this webhook.","examples":["Production webhook for ATS integration"]}},"type":"object","required":["url"],"title":"WebhookCreateRequest","description":"Register a webhook endpoint to receive interview event notifications."},"WebhookDeleteResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Deleted webhook ID."},"deleted":{"type":"boolean","title":"Deleted","default":true}},"type":"object","required":["id"],"title":"WebhookDeleteResponse","description":"Webhook deletion confirmation."},"WebhookDeliveryItem":{"properties":{"id":{"type":"string","title":"Id"},"event_type":{"type":"string","title":"Event Type"},"interview_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interview Id"},"attempt":{"type":"integer","title":"Attempt"},"success":{"type":"boolean","title":"Success"},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"delivery_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Delivery Time Ms"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","event_type","interview_id","attempt","success","status_code","error_message","delivery_time_ms","created_at"],"title":"WebhookDeliveryItem","description":"A webhook delivery attempt."},"WebhookDeliveryListResponse":{"properties":{"deliveries":{"items":{"$ref":"#/components/schemas/WebhookDeliveryItem"},"type":"array","title":"Deliveries"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["deliveries","total"],"title":"WebhookDeliveryListResponse","description":"Delivery history for a webhook."},"WebhookListResponse":{"properties":{"webhooks":{"items":{"$ref":"#/components/schemas/WebhookResponse"},"type":"array","title":"Webhooks","description":"Registered webhook endpoints."}},"type":"object","required":["webhooks"],"title":"WebhookListResponse","description":"List of registered webhooks."},"WebhookResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Webhook ID."},"url":{"type":"string","title":"Url","description":"Webhook URL."},"events":{"items":{"type":"string"},"type":"array","title":"Events","description":"Subscribed event types."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether this webhook is active."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret","description":"Signing secret (only returned on creation). Use to verify HMAC-SHA256 signature in X-BetterHire-Signature header."},"created_at":{"type":"string","title":"Created At","description":"ISO 8601 creation timestamp."}},"type":"object","required":["id","url","events","enabled","created_at"],"title":"WebhookResponse","description":"Registered webhook endpoint."},"WorkExperience":{"properties":{"company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"duration_months":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Months"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"key_achievements":{"items":{"type":"string"},"type":"array","title":"Key Achievements","default":[]},"technologies":{"items":{"type":"string"},"type":"array","title":"Technologies","default":[]}},"type":"object","title":"WorkExperience"},"_ChatCompletionsRequest":{"properties":{"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"messages":{"items":{"$ref":"#/components/schemas/_ChatMessage"},"type":"array","title":"Messages"},"stream":{"type":"boolean","title":"Stream","default":true}},"additionalProperties":true,"type":"object","title":"_ChatCompletionsRequest"},"_ChatMessage":{"properties":{"role":{"type":"string","title":"Role"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"}},"additionalProperties":true,"type":"object","required":["role"],"title":"_ChatMessage"},"backend__routers__interview_join__ScheduleInterviewRequest":{"properties":{"scheduled_at":{"type":"string","title":"Scheduled At"}},"type":"object","required":["scheduled_at"],"title":"ScheduleInterviewRequest","description":"Request to schedule an interview (candidate self-scheduling)"},"backend__routers__interview_join__ScheduleInterviewResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"},"scheduled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled At"}},"type":"object","required":["success","message"],"title":"ScheduleInterviewResponse","description":"Response after scheduling"},"backend__routers__interviews__models__InterviewListResponse":{"properties":{"interviews":{"items":{"$ref":"#/components/schemas/InterviewResponse"},"type":"array","title":"Interviews"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["interviews","total","limit","offset"],"title":"InterviewListResponse","description":"Interview list response"},"backend__routers__interviews__models__TranscriptResponse":{"properties":{"text":{"type":"string","title":"Text"},"segments":{"items":{"$ref":"#/components/schemas/backend__routers__interviews__models__TranscriptSegment"},"type":"array","title":"Segments"},"language":{"type":"string","title":"Language"},"duration_seconds":{"type":"integer","title":"Duration Seconds"},"num_speakers":{"type":"integer","title":"Num Speakers"},"provider":{"type":"string","title":"Provider"},"status":{"type":"string","title":"Status"},"roles":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Roles"},"peaks":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Peaks"}},"type":"object","required":["text","segments","language","duration_seconds","num_speakers","provider","status"],"title":"TranscriptResponse","description":"Full transcript with segments"},"backend__routers__interviews__models__TranscriptSegment":{"properties":{"speaker":{"type":"string","title":"Speaker"},"start":{"type":"number","title":"Start"},"end":{"type":"number","title":"End"},"text":{"type":"string","title":"Text"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"}},"type":"object","required":["speaker","start","end","text","confidence"],"title":"TranscriptSegment","description":"A segment of transcript with speaker label"},"backend__routers__job_roles__GenerateJDRequest":{"properties":{"job_title":{"type":"string","title":"Job Title"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"key_requirements":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Key Requirements"},"fix_instructions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Fix Instructions"}},"type":"object","required":["job_title"],"title":"GenerateJDRequest","description":"Request to generate a job description with AI"},"backend__routers__job_roles__JobRoleCreateRequest":{"properties":{"title":{"type":"string","title":"Title"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"location_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Type"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"employment_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employment Type"},"experience_level":{"type":"string","title":"Experience Level"},"min_years_experience":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Years Experience"},"max_years_experience":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Years Experience"},"jd_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Text"},"job_description_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Description Url"},"use_manual_competencies":{"type":"boolean","title":"Use Manual Competencies","default":true},"custom_competencies":{"anyOf":[{"items":{"$ref":"#/components/schemas/CompetencyItem"},"type":"array"},{"type":"null"}],"title":"Custom Competencies","default":[]},"quality_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quality Score"},"hiring_company_display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Display"},"hiring_company_pronounce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Pronounce"},"hiring_company_visibility":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Visibility"},"assessment_plan":{"anyOf":[{"$ref":"#/components/schemas/AssessmentPlan"},{"type":"null"}]},"tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag"},"default_framework_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Framework Code"}},"type":"object","required":["title","experience_level"],"title":"JobRoleCreateRequest","description":"Create job role request"},"backend__routers__job_roles__JobRoleListResponse":{"properties":{"job_roles":{"items":{"$ref":"#/components/schemas/backend__routers__job_roles__JobRoleResponse"},"type":"array","title":"Job Roles"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["job_roles","total","limit","offset"],"title":"JobRoleListResponse","description":"Job role list response"},"backend__routers__job_roles__JobRoleResponse":{"properties":{"id":{"type":"string","title":"Id"},"organization_id":{"type":"string","title":"Organization Id"},"title":{"type":"string","title":"Title"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"location_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Type"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"employment_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employment Type"},"experience_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Experience Level"},"min_years_experience":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Years Experience"},"max_years_experience":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Years Experience"},"jd_file_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd File Url"},"jd_file_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd File Name"},"jd_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jd Text"},"job_description_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Description Url"},"competencies_config":{"type":"object","title":"Competencies Config"},"use_manual_competencies":{"type":"boolean","title":"Use Manual Competencies"},"status":{"type":"string","title":"Status"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"interview_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Interview Count","default":0},"candidate_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidate Count","default":0},"quality_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quality Score"},"hiring_company_display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Display"},"hiring_company_pronounce":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Pronounce"},"hiring_company_visibility":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hiring Company Visibility"},"assessment_plan":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Assessment Plan"},"tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag"},"default_framework_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Framework Code"}},"type":"object","required":["id","organization_id","title","department","description","location_type","location","employment_type","experience_level","jd_file_url","jd_file_name","jd_text","job_description_url","competencies_config","use_manual_competencies","status","created_by","created_at","updated_at"],"title":"JobRoleResponse","description":"Job role response"},"backend__routers__public_api__InterviewListResponse":{"properties":{"interviews":{"items":{"$ref":"#/components/schemas/InterviewListItem"},"type":"array","title":"Interviews","description":"List of interviews."},"total":{"type":"integer","title":"Total","description":"Total number of interviews matching the query."},"limit":{"type":"integer","title":"Limit","description":"Number of results per page."},"offset":{"type":"integer","title":"Offset","description":"Current offset in the result set."}},"type":"object","required":["interviews","total","limit","offset"],"title":"InterviewListResponse","description":"Paginated list of interviews."},"backend__routers__public_job_roles__JobRoleCreateRequest":{"properties":{"title":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Title","description":"Omit to use the title read off the posting (`inferred.title`). 422 TITLE_REQUIRED when the text does not name the role either.","examples":["Senior Backend Engineer"]},"job_description":{"type":"string","maxLength":10000,"minLength":200,"title":"Job Description","description":"Full job description text. Competencies are extracted from it unless you supply them."},"competencies":{"anyOf":[{"items":{"$ref":"#/components/schemas/CompetencyInput"},"type":"array","maxItems":15},{"type":"null"}],"title":"Competencies","description":"Omit to have BetterHire extract 5–7 competencies from the job description. Supply your own to use them as given; they are validated against the interview engine's rules before anything is saved."},"experience_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Experience Level","description":"entry, mid, senior, lead or executive. Sets the seniority the candidate is judged against and how deep the interview probes. Defaults to mid.","examples":["senior"]},"company_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Company Name","description":"Hiring company shown to candidates, when it is not your own organisation (agencies)."}},"type":"object","required":["job_description"],"title":"JobRoleCreateRequest"},"backend__routers__public_job_roles__JobRoleListResponse":{"properties":{"job_roles":{"items":{"$ref":"#/components/schemas/JobRoleSummary"},"type":"array","title":"Job Roles"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["job_roles","total","limit","offset"],"title":"JobRoleListResponse"},"backend__routers__public_job_roles__JobRoleResponse":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"experience_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Experience Level"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"competencies":{"items":{"$ref":"#/components/schemas/Competency"},"type":"array","title":"Competencies"},"interview_ready":{"$ref":"#/components/schemas/InterviewReadiness"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"inferred":{"anyOf":[{"$ref":"#/components/schemas/InferredRole"},{"type":"null"}],"description":"What the extraction read off the posting. Null on roles created before inference existed."}},"type":"object","required":["id","title","competencies","interview_ready","created_at","updated_at"],"title":"JobRoleResponse"},"backend__routers__public_scheduling__ScheduleInterviewRequest":{"properties":{"candidate_name":{"type":"string","maxLength":200,"title":"Candidate Name","description":"Full name of the candidate.","examples":["Raj Patel"]},"candidate_email":{"type":"string","maxLength":254,"title":"Candidate Email","description":"Candidate's email address. Calendar invite will be sent here.","examples":["raj.patel@example.com"]},"role":{"type":"string","maxLength":200,"title":"Role","description":"Job title the candidate is interviewing for.","examples":["Senior Backend Engineer"]},"job_role_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Role Id","description":"Exact role id to schedule against. Wins over `role`. Pass this when your org has multiple roles sharing a title — otherwise an ambiguous `role` returns 409 listing the candidate role ids.","examples":["9ebd749c-3462-437a-8a17-81395b772568"]},"job_description":{"type":"string","maxLength":10000,"minLength":200,"title":"Job Description","description":"Full job description. Used to extract competencies for AI analysis after the interview."},"scheduled_at":{"type":"string","title":"Scheduled At","description":"Interview start time in ISO 8601 format (UTC). Must be in the future.","examples":["2026-04-05T14:00:00Z"]},"interviewer_email":{"type":"string","title":"Interviewer Email","description":"Email of the person conducting the interview. Calendar invite sent to them.","examples":["marcus@company.com"]},"duration_minutes":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Duration Minutes","description":"Interview duration in minutes.","default":45},"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url","description":"Existing Google Meet URL. If not provided and Google Calendar is connected, one is created automatically.","examples":["https://meet.google.com/abc-defg-hij"]},"resume_text":{"type":"string","maxLength":50000,"minLength":50,"title":"Resume Text","description":"Candidate's resume as plain text. Required for AI analysis — used to verify claims, identify skill gaps, and score competencies after the interview."},"notes":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"Notes","description":"Notes for the interviewer or internal reference."},"metadata":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Metadata","description":"Custom metadata to attach to this interview."}},"type":"object","required":["candidate_name","candidate_email","role","job_description","scheduled_at","interviewer_email","resume_text"],"title":"ScheduleInterviewRequest","description":"Schedule a human interview with automatic AI recording."},"backend__routers__public_scheduling__ScheduleInterviewResponse":{"properties":{"interview_id":{"type":"string","title":"Interview Id","description":"Interview ID. Use to check results after the meeting."},"candidate_id":{"type":"string","title":"Candidate Id","description":"Candidate ID for debrief and comparison."},"status":{"type":"string","title":"Status","description":"'scheduled'."},"scheduled_at":{"type":"string","title":"Scheduled At","description":"Confirmed interview time (ISO 8601)."},"meeting_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meeting Url","description":"Google Meet URL. Null if no calendar connected and no URL provided."},"message":{"type":"string","title":"Message","description":"Human-readable status message."}},"type":"object","required":["interview_id","candidate_id","status","scheduled_at","message"],"title":"ScheduleInterviewResponse","description":"Response after scheduling an interview."},"backend__routers__test_data__GenerateJDRequest":{"properties":{"role_title":{"type":"string","title":"Role Title"},"seniority":{"type":"string","title":"Seniority","default":"senior"},"company_type":{"type":"string","title":"Company Type","default":"tech"}},"type":"object","required":["role_title"],"title":"GenerateJDRequest"},"core__domains__public_api__scorecard__TranscriptResponse":{"properties":{"text":{"type":"string","title":"Text","description":"Full transcript as plain text."},"segments":{"items":{"$ref":"#/components/schemas/core__domains__public_api__scorecard__TranscriptSegment"},"type":"array","title":"Segments","description":"Speaker-labeled segments with timestamps. Use with recording_url for playback."},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds","description":"Total duration in seconds."}},"type":"object","required":["text"],"title":"TranscriptResponse","description":"Full interview transcript with speaker-labeled, timestamped segments."},"core__domains__public_api__scorecard__TranscriptSegment":{"properties":{"speaker":{"type":"string","title":"Speaker","description":"'interviewer' or 'candidate'.","examples":["candidate"]},"text":{"type":"string","title":"Text","description":"What was said."},"start_seconds":{"type":"number","title":"Start Seconds","description":"Start time in seconds.","examples":[12.5]},"end_seconds":{"type":"number","title":"End Seconds","description":"End time in seconds.","examples":[18.3]}},"type":"object","required":["speaker","text","start_seconds","end_seconds"],"title":"TranscriptSegment","description":"A speaker-labeled segment of the interview transcript."}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}