{"openapi":"3.1.0","info":{"title":"MatchingAgent API","description":"\n    AI-powered candidate-job matching API.\n    \n    ## Features\n    \n    - **Match candidates against offers** - Get scored matches with detailed analysis\n    - **Explainable results** - Understand why a match is good or bad\n    - **Flexible input** - Accept various data formats\n    \n    ## MVP Endpoints\n    \n    - `POST /v1/match` - Match a candidate against multiple offers\n    - `GET /v1/health` - Health check\n    \n    ## Coming Soon\n    \n    - `POST /v1/enrich` - Enrich candidate profiles with external data\n    - `POST /v1/parse-document` - Parse CV/resume documents\n    ","version":"mvp-1.0.0"},"paths":{"/v1/match-offers":{"post":{"tags":["matching"],"summary":"Match one candidate against multiple offers (max 20000)","description":"Match a single candidate against one or more job offers.\n\n    **Fast mode**: Bypasses Pydantic validation on offers for large requests.\n    Accepts raw JSON body, validates only candidate and options.\n\n    **Pagination for large requests (> page_size offers):**\n    - Layer 1 quick sorting is applied to ALL offers (no LLM)\n    - Layer 2 deep analysis is run on the first page (page_size offers)\n    - A `matching_session_id` is returned for fetching additional pages\n\n    **Response type:**\n    - <= page_size offers: Returns `MatchResultResponse` with direct deep analysis\n    - > page_size offers: Returns `PaginatedMatchResultResponse` with session ID","operationId":"match_candidate_v1_match_offers_post","responses":{"200":{"description":"Successful match results","content":{"application/json":{"schema":{}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/match-candidates":{"post":{"tags":["matching"],"summary":"Match multiple candidates against a single offer (max 5000)","description":"Match multiple candidates (max 5000) against a single job offer.\n    \n    **Input modes** (mutually exclusive):\n    - **Structured**: Provide candidate/offer fields directly (name, skills, experience_years, etc.)\n    - **Bulk**: Provide raw text/JSON in 'bulk' field - we extract structured data automatically\n    \n    **Pagination for large requests (> page_size candidates):**\n    - The `page_size` option (default 100, range: 5-100) controls pagination threshold\n    - Layer 1 quick sorting is applied to ALL candidates\n    - Layer 2 deep analysis is run on the first page (page_size candidates)\n    - A `matching_session_id` is returned for fetching additional pages\n    - Use `POST /v1/matching-session` with session_id and page number for more results\n    \n    **Response type:**\n    - <= page_size candidates: Returns `BatchMatchResultResponse` with direct deep analysis\n    - > page_size candidates: Returns `PaginatedMatchResultResponse` with session ID","operationId":"match_candidates_to_offer_v1_match_candidates_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchMatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful batch match results","content":{"application/json":{"schema":{}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/health":{"get":{"tags":["health"],"summary":"Health check","description":"Check the health status of the API and its components.","operationId":"health_check_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/matching-session":{"post":{"tags":["matching-session"],"summary":"Get matching session results","description":"Retrieve deeply analyzed results for a specific page of a matching session.\n    \n    **Request body:**\n    - `session_id`: The matching session ID (returned from /match-candidates or /match-offers)\n    - `page`: Page number (1-indexed, default 1)\n    - `include_analysis`: Include detailed analysis (default true)\n    - `lang`: Translation language code (optional)\n    \n    **Response type:**\n    - For match-candidates sessions: Returns `MatchingSessionPageResponse` with `CandidateMatchResponse` items\n    - For match-offers sessions: Returns `MatchOffersSessionPageResponse` with `OfferMatchResponse` items\n    \n    **Cache behavior:**\n    - If the page has been analyzed before, returns cached results instantly\n    - If not cached, runs Layer 2 deep analysis on the page's items (page_size items)\n    - Results are then cached for future requests\n    \n    **Performance:**\n    - Cached page: ~50ms\n    - New page: ~8-12 seconds (depends on bulk extraction needs)","operationId":"get_session_page_v1_matching_session_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchingSessionRequest"}}},"required":true},"responses":{"200":{"description":"Page results retrieved successfully","content":{"application/json":{"schema":{}}}},"404":{"description":"Session not found or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Invalid page number","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/v1/api-keys":{"get":{"tags":["api-keys"],"summary":"List Api Keys","description":"List all API keys for the authenticated user.\n\nArgs:\n    auth: Authentication context\n    session: Database session\n    \nReturns:\n    List of API keys","operationId":"list_api_keys_v1_v1_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ApiKeyResponse"},"type":"array","title":"Response List Api Keys V1 V1 Api Keys Get"}}}}}},"post":{"tags":["api-keys"],"summary":"Create Api Key","description":"Create a new API key for the authenticated user.\n\nArgs:\n    data: API key creation data\n    auth: Authentication context\n    session: Database session\n    \nReturns:\n    New API key (full key only shown once)","operationId":"create_api_key_v1_v1_api_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/v1/api-keys/{key_id}":{"delete":{"tags":["api-keys"],"summary":"Revoke Api Key","description":"Revoke an API key.\n\nArgs:\n    key_id: API key UUID\n    auth: Authentication context\n    session: Database session\n    \nRaises:\n    HTTPException: 404 if key not found or not owned by user","operationId":"revoke_api_key_v1_v1_api_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/v1/usage":{"get":{"tags":["usage"],"summary":"Get Current Usage","description":"Get current billing period usage summary.\n\nArgs:\n    auth: Authentication context\n    session: Database session\n    \nReturns:\n    Usage summary","operationId":"get_current_usage_v1_v1_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageSummaryResponse"}}}}}}},"/v1/v1/usage/history":{"get":{"tags":["usage"],"summary":"Get Usage History","description":"Get paginated usage history.\n\nArgs:\n    page: Page number (1-indexed)\n    page_size: Records per page (max 100)\n    start_date: Optional start date filter\n    end_date: Optional end date filter\n    auth: Authentication context\n    session: Database session\n    \nReturns:\n    Paginated usage records","operationId":"get_usage_history_v1_v1_usage_history_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number","default":1,"title":"Page"},"description":"Page number"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Records per page","default":50,"title":"Page Size"},"description":"Records per page"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter from date","title":"Start Date"},"description":"Filter from date"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter to date","title":"End Date"},"description":"Filter to date"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/v1/internal/users":{"post":{"tags":["users"],"summary":"Create Or Sync User","description":"Create a new user or sync existing user data.\n\nCalled by Next.js after OAuth authentication.\nIf user already exists (by email or provider_id), updates their info.\n\nArgs:\n    data: User data from OAuth provider\n    session: Database session\n    \nReturns:\n    User information","operationId":"create_or_sync_user_v1_v1_internal_users_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/v1/internal/users/{user_id}/usage":{"get":{"tags":["users"],"summary":"Get User Usage","description":"Get usage statistics for a user.\n\nReturns current billing period usage.\n\nArgs:\n    user_id: User UUID\n    auth: Authentication context\n    session: Database session\n    \nReturns:\n    Usage statistics\n    \nRaises:\n    HTTPException: 403 if user tries to access another user's stats","operationId":"get_user_usage_v1_v1_internal_users__user_id__usage_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/v1/internal/users/{user_id}/pay-as-you-go":{"patch":{"tags":["users"],"summary":"Toggle Pay As You Go","description":"Enable or disable pay-as-you-go overflow for a user's subscription.\n\nWhen enabled, allows users to continue using the API beyond their\nmonthly quota at pay-as-you-go rates (€0.001/match).\n\nArgs:\n    user_id: User UUID\n    data: Request with enabled flag\n    auth: Authentication context\n    session: Database session\n    \nReturns:\n    Updated pay-as-you-go status\n    \nRaises:\n    HTTPException: 403 if user tries to modify another user's settings\n    HTTPException: 404 if no active subscription found","operationId":"toggle_pay_as_you_go_v1_v1_internal_users__user_id__pay_as_you_go_patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayAsYouGoRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayAsYouGoResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["users"],"summary":"Get Pay As You Go Status","description":"Get pay-as-you-go overflow status for a user.\n\nArgs:\n    user_id: User UUID\n    auth: Authentication context\n    session: Database session\n    \nReturns:\n    Current pay-as-you-go status\n    \nRaises:\n    HTTPException: 403 if user tries to access another user's settings","operationId":"get_pay_as_you_go_status_v1_v1_internal_users__user_id__pay_as_you_go_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayAsYouGoResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/stripe":{"post":{"tags":["webhooks"],"summary":"Handle Stripe webhook events","description":"Endpoint for Stripe to send webhook events.\n    \n    Handles:\n    - invoice.payment_succeeded - Updates subscription status\n    - customer.subscription.deleted - Marks subscription as canceled\n    - customer.subscription.updated - Updates subscription details\n    \n    **Important**: This endpoint verifies the Stripe signature to ensure\n    the webhook is legitimate.","operationId":"handle_stripe_webhook_v1_webhooks_stripe_post","parameters":[{"name":"Stripe-Signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe-Signature"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/internal/offers/index":{"post":{"tags":["index"],"summary":"Add offers to FAISS index","description":"Add or update offers in the FAISS vector index.\n\nOne offer that cannot be embedded is skipped and listed in `failed_ids`; the\nrest of the batch is indexed (2026-10-05). 500 only when nothing could be.","operationId":"index_offers_v1_internal_offers_index_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexOffersRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexOffersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["index"],"summary":"Remove offers from FAISS index","description":"Remove offers from the FAISS vector index.","operationId":"remove_offers_v1_internal_offers_index_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveOffersRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveOffersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/internal/offers/index/info":{"get":{"tags":["index"],"summary":"Get FAISS index info","description":"Get information about the FAISS index.","operationId":"index_info_v1_internal_offers_index_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexInfoResponse"}}}}}}},"/v1/internal/offers/index/rebuild":{"post":{"tags":["index"],"summary":"Rebuild FAISS index from GetYourJob DB","description":"Trigger a full FAISS index rebuild from GetYourJob database.","operationId":"rebuild_index_v1_internal_offers_index_rebuild_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RebuildRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RebuildResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/internal/model/train":{"post":{"tags":["model"],"summary":"Trigger model training","description":"Trigger LightGBM model training from labelled data.","operationId":"trigger_training_v1_internal_model_train_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/internal/model/status":{"get":{"tags":["model"],"summary":"Get model status and metrics","description":"Get current model status, metrics, and suggested weights.","operationId":"model_status_v1_internal_model_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelStatusResponse"}}}}}}},"/v1/internal/eval/calibration":{"get":{"tags":["eval"],"summary":"Inspect score calibrator state and distributions","description":"Returns metadata for the bundled isotonic calibrators (production `overall_score` + dev-only `quick_score`), an anchor-point curve so dashboards can render the mapping, and (optionally) a side-by-side raw vs calibrated histogram for the current eval label set. Default-OFF: when MA_ENABLE_CALIBRATION is unset, `overall_calibrator.enabled` reads false and the production scoring is unchanged. The quick calibrator is loadable regardless — it's never applied in serving (would shift Layer-1 sort thresholds), only exposed here for distribution comparison.","operationId":"get_calibration_state_v1_internal_eval_calibration_get","parameters":[{"name":"histogram","in":"query","required":false,"schema":{"type":"boolean","description":"When true, includes a side-by-side raw vs calibrated histogram computed on the eval label set.","default":false,"title":"Histogram"},"description":"When true, includes a side-by-side raw vs calibrated histogram computed on the eval label set."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Calibration State V1 Internal Eval Calibration Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/internal/eval/latest":{"get":{"tags":["eval"],"summary":"Fetch the most recent eval reports","description":"Returns the most recent eval reports (written by ``scripts/evaluate.py``) as a JSON array, newest-first. Used by the GetYourJob admin eval dashboard. Use ``days`` for a time window (7/30/90) and ``limit`` as a hard cap.","operationId":"get_latest_eval_reports_v1_internal_eval_latest_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"description":"Max number of reports to return (0 returns []).","default":90,"title":"Limit"},"description":"Max number of reports to return (0 returns [])."},{"name":"days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":365,"minimum":0},{"type":"null"}],"description":"Optional time window in days. When set, returns reports whose embedded timestamp is within the last N days. Combine with ``limit`` for a safety cap.","title":"Days"},"description":"Optional time window in days. When set, returns reports whose embedded timestamp is within the last N days. Combine with ``limit`` for a safety cap."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Latest Eval Reports V1 Internal Eval Latest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/enrich":{"post":{"tags":["enrichment"],"summary":"Enrich candidate profile","description":"🔲 STUB: Not implemented in MVP. Enrich a candidate profile with external data.","operationId":"enrich_candidate_v1_enrich_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Enrich Candidate V1 Enrich Post"}}}}}}},"/v1/parse-document":{"post":{"tags":["document-parsing"],"summary":"Parse CV/resume document","description":"🔲 STUB: Not implemented in MVP. Parse a CV/resume document and extract structured data.","operationId":"parse_document_v1_parse_document_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Parse Document V1 Parse Document Post"}}}}}}},"/":{"get":{"tags":["root"],"summary":"Root","description":"Root endpoint with API information.","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Root  Get"}}}}}}}},"components":{"schemas":{"ApiKeyResponse":{"properties":{"id":{"type":"string","title":"Id"},"key_prefix":{"type":"string","title":"Key Prefix"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"created_at":{"type":"string","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","key_prefix","name","created_at","last_used_at","revoked_at","is_active"],"title":"ApiKeyResponse","description":"API key information (without full key)."},"BatchMatchRequest":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/CandidateRequest"},"type":"array","maxItems":20000,"minItems":1,"title":"Candidates","description":"Candidates to match against the offer (max 20000, paginated)"},"offer":{"$ref":"#/components/schemas/OfferRequest","description":"Single offer to match candidates against"},"options":{"$ref":"#/components/schemas/MatchingOptions","description":"Match options"}},"type":"object","required":["candidates","offer"],"title":"BatchMatchRequest","description":"Request body for the /match-candidates endpoint.\n\nContains multiple candidates (max 10000) and a single offer to match against.\n\nFor requests with > page_size candidates (page_size default 100, range: 5-100):\n- Layer 1 quick sorting is applied to all candidates\n- Layer 2 deep analysis is run on the first page (page_size candidates)\n- A matching_session_id is returned for pagination\n- Use POST /v1/matching-session with session_id and page for additional pages\n\nBoth candidates and offer support structured mode or bulk mode (mutually exclusive).","examples":[{"description":"Structured mode for candidates and offer","value":{"candidates":[{"experience_years":5,"external_id":"candidate-001","headline":"Senior Full-Stack Developer","name":"John Doe","seniority":"senior","skills":["React","Node.js","TypeScript"]},{"experience_years":3,"external_id":"candidate-002","name":"Jane Smith","seniority":"mid","skills":["Python","Django","PostgreSQL"]}],"offer":{"external_id":"offer-123","required_skills":["React","Node.js"],"seniority_required":"senior","title":"Full-Stack Developer"},"options":{"include_analysis":true,"min_score_threshold":5.0}}},{"description":"Bulk mode - raw CVs and job posting","value":{"candidates":[{"bulk":"John Doe - 5 years React, Node.js developer...","external_id":"candidate-001"},{"bulk":"Jane Smith - Python backend engineer with 3 years...","external_id":"candidate-002"}],"offer":{"bulk":"We're hiring a senior Full-Stack Developer...","external_id":"offer-123"}}}]},"CandidateMobilityRequest":{"properties":{"radius_km":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Radius Km","description":"Travel radius (km) for on-site / hybrid offers; null = none stated."},"onsite_unlimited":{"type":"boolean","title":"Onsite Unlimited","description":"On-site / hybrid offers anywhere (« sans limite »).","default":false},"remote_reach":{"anyOf":[{"type":"string","enum":["NATIONAL","INTERNATIONAL"]},{"type":"null"}],"title":"Remote Reach","description":"Remote offers: NATIONAL (own country) or INTERNATIONAL (any country)."},"accepted_work_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Accepted Work Types","description":"Work types accepted (remote / hybrid / onsite)."}},"type":"object","title":"CandidateMobilityRequest","description":"The candidate's declared reach (GetYourJob offerReach.ts): `radius_km` for\non-site / hybrid offers (or `onsite_unlimited`), `remote_reach` NATIONAL |\nINTERNATIONAL for remote ones, `accepted_work_types` (remote / hybrid / onsite).\nRead by the analysis prose and the Layer 2 distance multiplier. Lenient: an unknown value is dropped, never a 422\nthat would fail the whole match request."},"CandidateRequest":{"properties":{"bulk":{"anyOf":[{"type":"string","maxLength":100000},{"type":"null"}],"title":"Bulk","description":"Raw text or JSON blob to extract candidate data from. Can be a CV, LinkedIn profile text, or any unstructured data. Mutually exclusive with other fields - use bulk OR structured fields, not both."},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Customer's external ID for data reconciliation (any format). Always allowed."},"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Name","description":"Candidate name"},"headline":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Headline","description":"Professional headline"},"summary":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"Summary","description":"Bio/summary"},"experience_years":{"anyOf":[{"type":"number","maximum":50.0,"minimum":0.0},{"type":"null"}],"title":"Experience Years","description":"Total years of experience"},"current_role":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Current Role","description":"Current job title"},"seniority":{"anyOf":[{"type":"string","pattern":"^(junior|mid|senior|lead|principal)$"},{"type":"null"}],"title":"Seniority","description":"Seniority level: junior, mid, senior, lead, or principal"},"location":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Location","description":"Current location"},"location_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Location Lat","description":"Location latitude (decimal degrees)"},"location_lng":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Location Lng","description":"Location longitude (decimal degrees)"},"willing_to_relocate":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Willing To Relocate","description":"Whether candidate is willing to relocate"},"preferred_remote":{"anyOf":[{"type":"string","pattern":"^(remote|hybrid|onsite|flexible)$"},{"type":"null"}],"title":"Preferred Remote","description":"Candidate's preferred remote work arrangement"},"salary_expectation_min":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Salary Expectation Min","description":"Minimum expected salary (annual, in local currency)"},"salary_expectation_max":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Salary Expectation Max","description":"Maximum expected salary (annual, in local currency)"},"salary_currency":{"anyOf":[{"type":"string","maxLength":3},{"type":"null"}],"title":"Salary Currency","description":"Currency code for salary (e.g., EUR, USD)"},"daily_rate_expectation":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Daily Rate Expectation","description":"Expected daily rate (freelance), compared only with a daily-rate offer"},"skills":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Skills","description":"List of all skills (backward compat — use main_skills + secondary_skills when possible)"},"main_skills":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Main Skills","description":"Primary/core technical skills (weighted higher in matching)"},"secondary_skills":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Secondary Skills","description":"Secondary/tool skills (weighted lower in matching)"},"main_skills_ranked":{"anyOf":[{"items":{"$ref":"#/components/schemas/RankedSkillRequest"},"type":"array"},{"type":"null"}],"title":"Main Skills Ranked","description":"Primary skills with their priority order (1 = strongest). When provided, supersedes positional ordering of main_skills."},"secondary_skills_rated":{"anyOf":[{"items":{"$ref":"#/components/schemas/RatedSkillRequest"},"type":"array"},{"type":"null"}],"title":"Secondary Skills Rated","description":"Secondary skills with self-reported proficiency 0-9. Layer 2 SkillsAgent scales each match by rating/9."},"interested_roles":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Interested Roles","description":"Candidate's explicit role preferences as GetYourJob RoleType enum values (e.g. ['PRODUCT_OWNER', 'CONSULTANT', 'BACKEND']; also 'PRODUCT_DESIGNER' for product/UX/UI designers and 'BUSINESS_DEVELOPER' for IT sales). When set, matches are biased toward these roles: a bonus on on-role offers, a penalty on off-role offers. Free strings: a value this service does not know is accepted and ignored (no bonus, no penalty). Default null preserves prior scoring behavior."},"mobility":{"anyOf":[{"$ref":"#/components/schemas/CandidateMobilityRequest"},{"type":"null"}],"description":"Declared reach: travel radius / unlimited for on-site and hybrid offers, remote reach for remote ones. An offer inside it is never described as needing a relocation, and its Layer 2 distance penalty is halved (Layer 1 unchanged). Allowed next to bulk, like external_id."},"experience":{"anyOf":[{"items":{"$ref":"#/components/schemas/ExperienceRequest"},"type":"array"},{"type":"null"}],"title":"Experience","description":"Detailed experience history"},"education":{"anyOf":[{"items":{"$ref":"#/components/schemas/EducationRequest"},"type":"array"},{"type":"null"}],"title":"Education","description":"Education history"}},"type":"object","title":"CandidateRequest","description":"Candidate data in a match request.\n\nSupports two mutually exclusive input modes:\n1. **Structured mode**: Provide fields directly (name, skills, experience_years, etc.)\n2. **Bulk mode**: Provide raw text/JSON in 'bulk' field - we extract structured data\n\nCannot mix bulk with other fields. Use one mode or the other.","examples":[{"description":"Structured mode - provide fields directly","value":{"experience_years":5,"external_id":"candidate-12345","headline":"Senior Full-Stack Developer","name":"John Doe","seniority":"senior","skills":["React","Node.js","TypeScript","PostgreSQL","AWS"]}},{"description":"Bulk mode - raw CV text for extraction","value":{"bulk":"John Doe - Senior Full-Stack Developer\n\n5 years experience with React, Node.js, TypeScript...","external_id":"candidate-12345"}}]},"CreateApiKeyRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"environment":{"type":"string","title":"Environment","default":"live"}},"type":"object","title":"CreateApiKeyRequest","description":"Request to create a new API key."},"CreateApiKeyResponse":{"properties":{"id":{"type":"string","title":"Id"},"key":{"type":"string","title":"Key"},"key_prefix":{"type":"string","title":"Key Prefix"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"created_at":{"type":"string","title":"Created At"},"warning":{"type":"string","title":"Warning","default":"Save this key securely. It will not be shown again."}},"type":"object","required":["id","key","key_prefix","name","created_at"],"title":"CreateApiKeyResponse","description":"Response when creating a new API key (includes full key once)."},"CreateUserRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"picture_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Picture Url"},"provider":{"type":"string","title":"Provider","default":"google"},"provider_id":{"type":"string","title":"Provider Id"}},"type":"object","required":["email","provider_id"],"title":"CreateUserRequest","description":"Request to create or sync a user."},"EducationRequest":{"properties":{"degree":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Degree","description":"Degree name"},"school":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"School","description":"School name"},"year":{"anyOf":[{"type":"integer","maximum":2100.0,"minimum":1950.0},{"type":"null"}],"title":"Year","description":"Graduation year"}},"type":"object","title":"EducationRequest","description":"Education entry in a candidate request."},"ErrorResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always false for errors","default":false},"error":{"additionalProperties":true,"type":"object","title":"Error","description":"Error details"}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Error response format.","example":{"error":{"code":"VALIDATION_ERROR","details":[{"error":"At least one skill is required","field":"candidate.skills"}],"message":"Invalid request format"},"success":false}},"ExperienceRequest":{"properties":{"title":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Title","description":"Job title"},"company":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Company","description":"Company name"},"duration_months":{"anyOf":[{"type":"integer","maximum":600.0,"minimum":0.0},{"type":"null"}],"title":"Duration Months","description":"Duration in months"},"description":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"Description","description":"Role description"},"skills_used":{"items":{"type":"string"},"type":"array","title":"Skills Used","description":"Skills used in this role"}},"type":"object","title":"ExperienceRequest","description":"Experience entry in a candidate request."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Overall health status"},"version":{"type":"string","title":"Version","description":"API version"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"Current timestamp"},"components":{"additionalProperties":{"type":"string"},"type":"object","title":"Components","description":"Component statuses"}},"type":"object","required":["status","version","timestamp","components"],"title":"HealthResponse","description":"Response from the health check endpoint.","example":{"components":{"api":"ok","llm":"ok"},"status":"healthy","timestamp":"2025-01-08T15:30:00Z","version":"mvp-1.0.0"}},"IndexInfoResponse":{"properties":{"available":{"type":"boolean","title":"Available"},"total_offers":{"type":"integer","title":"Total Offers"},"dimension":{"type":"integer","title":"Dimension"},"faiss_prefilter_enabled":{"type":"boolean","title":"Faiss Prefilter Enabled"}},"type":"object","required":["available","total_offers","dimension","faiss_prefilter_enabled"],"title":"IndexInfoResponse","description":"Response with index information."},"IndexOffersRequest":{"properties":{"offers":{"items":{"additionalProperties":true,"type":"object"},"type":"array","maxItems":10000,"minItems":1,"title":"Offers","description":"List of offer dicts with external_id, title, required_skills, etc. Recommended: <= 500 per request (~30 ms per offer)."}},"type":"object","required":["offers"],"title":"IndexOffersRequest","description":"Request to add offers to the FAISS index.\n\nBatch size: the local MiniLM embeds ~30 ms per offer on the production CPU, so a\nrequest of N offers takes about N x 30 ms plus the index save. Send at most ~500\noffers per request (~15 s); 2 000-3 300 per request (the nightly sync until\n2026-10-05) takes one to two minutes and trips client timeouts. The limit below\nis a hard ceiling, not a recommended size."},"IndexOffersResponse":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"indexed":{"type":"integer","title":"Indexed","description":"Number of offers indexed"},"total_in_index":{"type":"integer","title":"Total In Index","description":"Total offers now in index"},"failed":{"type":"integer","title":"Failed","description":"Offers skipped (could not be embedded)","default":0},"failed_ids":{"items":{"type":"string"},"type":"array","title":"Failed Ids","description":"external_ids of the skipped offers; the rest of the batch is indexed"}},"type":"object","required":["indexed","total_in_index"],"title":"IndexOffersResponse","description":"Response from indexing offers."},"MatchingOptions":{"properties":{"min_score_threshold":{"type":"number","maximum":10.0,"minimum":0.0,"title":"Min Score Threshold","description":"Minimum score to include in results","default":0.0},"max_results":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":1.0},{"type":"null"}],"title":"Max Results","description":"Maximum number of results to return"},"include_analysis":{"type":"boolean","title":"Include Analysis","description":"Include detailed analysis in response","default":true},"lang":{"anyOf":[{"type":"string","maxLength":5,"minLength":2},{"type":"null"}],"title":"Lang","description":"Target language code for analysis translation (e.g., 'fr', 'es', 'de', 'zh'). If not specified, analysis is returned in English."},"page_size":{"type":"integer","maximum":100.0,"minimum":5.0,"title":"Page Size","description":"Number of items per page for pagination (5-100). If total items <= page_size, deep analysis runs on all items directly.","default":100},"location_scope":{"anyOf":[{"type":"string","pattern":"^(local|national|international)$"},{"type":"null"}],"title":"Location Scope","description":"Location scope: 'local' (within max_distance_km), 'national' (same country), 'international' (all)"},"max_distance_km":{"anyOf":[{"type":"number","maximum":1000.0,"minimum":0.0},{"type":"null"}],"title":"Max Distance Km","description":"Maximum distance in km for 'local' scope (only used when location_scope='local')"},"work_type_filter":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Work Type Filter","description":"Work type filter: list of 'remote', 'hybrid', 'onsite'. Empty/None = all types"},"role_filter":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Role Filter","description":"RoleType enum values to prioritize in scoring (same vocabulary as candidate.interested_roles, incl. PRODUCT_DESIGNER and BUSINESS_DEVELOPER). Offers matching none of the known values get a reduced score. Unknown values are ignored; a filter made only of unknown values applies no penalty."},"strict_location":{"type":"boolean","title":"Strict Location","description":"If true, hard-reject items outside location constraints instead of soft-penalizing","default":false},"contract_type_filter":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Contract Type Filter","description":"Contract type filter: list of 'freelance', 'cdi'. Empty/None = all types"},"ab_variant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ab Variant","description":"Experiment variant assigned upstream by GetYourJob (typical values: 'control', 'treatment'). The server accepts any string for forward-compat and echoes it back in the response metadata. It DOES NOT branch matching behavior on this value — it's logged for offline analysis only."}},"type":"object","title":"MatchingOptions","description":"Shared options for match requests (match-offers and match-candidates)."},"MatchingSessionRequest":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"Matching session ID (returned from /match-candidates when items > page_size)","examples":["ms-a1b2c3d4-e5f6-7890-abcd-ef1234567890"]},"page":{"type":"integer","minimum":1.0,"title":"Page","description":"Page number (1-indexed). Default is 1.","default":1},"include_analysis":{"type":"boolean","title":"Include Analysis","description":"Include detailed analysis in response","default":true},"lang":{"anyOf":[{"type":"string","maxLength":5,"minLength":2},{"type":"null"}],"title":"Lang","description":"Target language code for analysis translation (e.g., 'fr', 'es'). Uses session's original language if not specified."}},"type":"object","required":["session_id"],"title":"MatchingSessionRequest","description":"Request body for fetching matching session results.","example":{"include_analysis":true,"lang":"fr","page":2,"session_id":"ms-a1b2c3d4-e5f6-7890-abcd-ef1234567890"}},"ModelStatusResponse":{"properties":{"model_available":{"type":"boolean","title":"Model Available"},"is_training":{"type":"boolean","title":"Is Training"},"last_trained_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Trained At"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"metrics":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metrics"},"feature_importances":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Feature Importances"},"suggested_weights":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Suggested Weights"},"labels_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Labels Used"}},"type":"object","required":["model_available","is_training"],"title":"ModelStatusResponse","description":"Response with model status and metrics."},"OfferRequest":{"properties":{"bulk":{"anyOf":[{"type":"string","maxLength":100000},{"type":"null"}],"title":"Bulk","description":"Raw text or JSON blob to extract offer data from. Can be a job posting, job description text, or any unstructured data. Mutually exclusive with other fields - use bulk OR structured fields, not both."},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Customer's external ID for data reconciliation (any format). Always allowed."},"title":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Title","description":"Job title"},"company":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Company","description":"Company name"},"description":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}],"title":"Description","description":"Full job description"},"required_skills":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Required Skills","description":"Must-have skills"},"nice_to_have_skills":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Nice To Have Skills","description":"Bonus skills"},"primary_skill":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Primary Skill","description":"Explicit must-have skill that triggers the primary-skill-missing penalty. When None, the first non-sentinel required skill is used; the role-aware resolver (title/domain) applies only when `e1_primary_resolver_enabled` is on."},"seniority_required":{"anyOf":[{"type":"string","pattern":"^(junior|mid|senior|lead|principal)$"},{"type":"null"}],"title":"Seniority Required","description":"Required seniority level: junior, mid, senior, lead, or principal"},"experience_years_min":{"anyOf":[{"type":"integer","maximum":50.0,"minimum":0.0},{"type":"null"}],"title":"Experience Years Min","description":"Minimum years of experience"},"experience_years_max":{"anyOf":[{"type":"integer","maximum":50.0,"minimum":0.0},{"type":"null"}],"title":"Experience Years Max","description":"Maximum years of experience"},"location":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Location","description":"Job location"},"location_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Location Lat","description":"Location latitude (decimal degrees)"},"location_lng":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Location Lng","description":"Location longitude (decimal degrees)"},"remote":{"anyOf":[{"type":"string","pattern":"^(remote|hybrid|onsite)$"},{"type":"null"}],"title":"Remote","description":"Remote work policy"},"contract_type":{"anyOf":[{"type":"string","pattern":"^(freelance|cdi|both)$"},{"type":"null"}],"title":"Contract Type","description":"Contract type: freelance, cdi, or both"},"salary_min":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Salary Min","description":"Minimum salary offered (annual, in local currency)"},"salary_max":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Salary Max","description":"Maximum salary offered (annual, in local currency)"},"salary_currency":{"anyOf":[{"type":"string","maxLength":3},{"type":"null"}],"title":"Salary Currency","description":"Currency code for salary (e.g., EUR, USD)"},"salary_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Salary Period","description":"'daily' when the offer pays a day rate (daily_rate_min/max); annual otherwise"},"daily_rate_min":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Daily Rate Min","description":"Day rate, low end"},"daily_rate_max":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Daily Rate Max","description":"Day rate, high end"},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry","description":"Industry/domain (e.g., fintech, healthtech, ecommerce)"},"company_size":{"anyOf":[{"type":"string","pattern":"^(startup|smb|midmarket|enterprise)$"},{"type":"null"}],"title":"Company Size","description":"Company size category"}},"type":"object","title":"OfferRequest","description":"Job offer data in a match request.\n\nSupports two mutually exclusive input modes:\n1. **Structured mode**: Provide fields directly (title, required_skills, etc.)\n2. **Bulk mode**: Provide raw text/JSON in 'bulk' field - we extract structured data\n\nCannot mix bulk with other fields. Use one mode or the other.","examples":[{"description":"Structured mode - provide fields directly","value":{"company":"StartupXYZ","experience_years_max":7,"experience_years_min":3,"external_id":"offer-67890","nice_to_have_skills":["TypeScript","AWS"],"required_skills":["React","Node.js"],"seniority_required":"senior","title":"Full-Stack Developer"}},{"description":"Bulk mode - raw job posting for extraction","value":{"bulk":"Full-Stack Developer at StartupXYZ\n\nWe're looking for a senior developer with 3-7 years experience in React and Node.js...","external_id":"offer-67890"}}]},"PayAsYouGoRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"PayAsYouGoRequest","description":"Request to toggle pay-as-you-go overflow."},"PayAsYouGoResponse":{"properties":{"pay_as_you_go_enabled":{"type":"boolean","title":"Pay As You Go Enabled"},"message":{"type":"string","title":"Message"}},"type":"object","required":["pay_as_you_go_enabled","message"],"title":"PayAsYouGoResponse","description":"Response for pay-as-you-go toggle."},"RankedSkillRequest":{"properties":{"technology":{"type":"string","maxLength":100,"title":"Technology","description":"Skill / technology name"},"order":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Order","description":"Priority order; 1 is strongest"}},"type":"object","required":["technology","order"],"title":"RankedSkillRequest","description":"A primary/main skill paired with its priority order (1 = strongest)."},"RatedSkillRequest":{"properties":{"technology":{"type":"string","maxLength":100,"title":"Technology","description":"Skill / technology name"},"rating":{"type":"integer","maximum":9.0,"minimum":0.0,"title":"Rating","description":"Proficiency rating 0-9 (9 = expert)"}},"type":"object","required":["technology","rating"],"title":"RatedSkillRequest","description":"A secondary skill paired with the candidate's self-reported proficiency."},"RebuildRequest":{"properties":{"db_url":{"type":"string","title":"Db Url","description":"PostgreSQL connection URL for GetYourJob DB"}},"type":"object","required":["db_url"],"title":"RebuildRequest","description":"Request to rebuild FAISS index from GetYourJob DB."},"RebuildResponse":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"type":"string","title":"Message"},"total_indexed":{"type":"integer","title":"Total Indexed","default":0},"rebuild_started":{"type":"boolean","title":"Rebuild Started","default":false}},"type":"object","required":["message"],"title":"RebuildResponse","description":"Response from index rebuild."},"RemoveOffersRequest":{"properties":{"offer_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Offer Ids","description":"List of offer external_ids to remove"}},"type":"object","required":["offer_ids"],"title":"RemoveOffersRequest","description":"Request to remove offers from the FAISS index."},"RemoveOffersResponse":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"removed":{"type":"integer","title":"Removed","description":"Number of offers removed"},"total_in_index":{"type":"integer","title":"Total In Index","description":"Total offers now in index"}},"type":"object","required":["removed","total_in_index"],"title":"RemoveOffersResponse","description":"Response from removing offers."},"TrainRequest":{"properties":{"getyourjob_url":{"type":"string","title":"Getyourjob Url","description":"GetYourJob base URL for fetching labels","default":"http://localhost:3001"},"export_key":{"type":"string","title":"Export Key","description":"API key for GetYourJob labelling export endpoint"},"min_labels":{"type":"integer","title":"Min Labels","description":"Minimum labels required to train","default":100}},"type":"object","required":["export_key"],"title":"TrainRequest","description":"Request to trigger model training."},"TrainResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"},"training_started":{"type":"boolean","title":"Training Started","default":false}},"type":"object","required":["success","message"],"title":"TrainResponse","description":"Response from training trigger."},"UsageHistoryResponse":{"properties":{"records":{"items":{"$ref":"#/components/schemas/UsageRecordResponse"},"type":"array","title":"Records"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["records","total_count","page","page_size","has_more"],"title":"UsageHistoryResponse","description":"Paginated usage history."},"UsageRecordResponse":{"properties":{"id":{"type":"string","title":"Id"},"endpoint":{"type":"string","title":"Endpoint"},"matches_count":{"type":"integer","title":"Matches Count"},"cost_eur":{"type":"number","title":"Cost Eur"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id"},"created_at":{"type":"string","title":"Created At"},"api_key_prefix":{"type":"string","title":"Api Key Prefix"}},"type":"object","required":["id","endpoint","matches_count","cost_eur","request_id","created_at","api_key_prefix"],"title":"UsageRecordResponse","description":"Single usage record."},"UsageStatsResponse":{"properties":{"user_id":{"type":"string","title":"User Id"},"plan_tier":{"type":"string","title":"Plan Tier"},"monthly_quota":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Quota"},"matches_used":{"type":"integer","title":"Matches Used"},"cost_eur":{"type":"number","title":"Cost Eur"},"period_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Start"},"period_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period End"}},"type":"object","required":["user_id","plan_tier","monthly_quota","matches_used","cost_eur","period_start","period_end"],"title":"UsageStatsResponse","description":"User usage statistics."},"UsageSummaryResponse":{"properties":{"total_matches":{"type":"integer","title":"Total Matches"},"total_cost_eur":{"type":"number","title":"Total Cost Eur"},"period_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Start"},"period_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period End"},"plan_tier":{"type":"string","title":"Plan Tier"}},"type":"object","required":["total_matches","total_cost_eur","period_start","period_end","plan_tier"],"title":"UsageSummaryResponse","description":"Summary of current period usage."},"UserResponse":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"picture_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Picture Url"},"provider":{"type":"string","title":"Provider"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","email","name","picture_url","provider","created_at"],"title":"UserResponse","description":"User information response."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}