{"openapi":"3.0.0","info":{"title":"Purple Suite HRBP API","description":"HR Business Partner (HRBP) source system — Talent Insights & Talent Review, and Employee Relations case management. Data layer for talent reviews, 9-box employee assessments, performance reviews, succession planning, metric snapshots, and ER cases/allegations/involved parties (SN sn_hr_er_* parity). ServiceNow-parity contract endpoints (resolve_filters, available_metrics, preview_review/commit_review, query) and the tp_* MCP tools are served alongside these collections. References employees in the Purple HRS (HRIS) app by employee_id. Choice fields use a readable canonical value; the originating ServiceNow code is noted in each field description.\n\n---\n\n**Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.","version":"1.0.0"},"servers":[{"url":"https://marketplace.moveworks.com/api/purple-suite/hrbp"}],"security":[{"patAuth":[]}],"components":{"securitySchemes":{"patAuth":{"type":"http","scheme":"bearer"}},"parameters":{"InstanceId":{"in":"header","name":"X-Instance-ID","required":true,"schema":{"type":"string"}},"ImpersonateUser":{"in":"header","name":"X-PS-Impersonate-User","required":false,"schema":{"type":"string","format":"email"},"description":"Optional. Act as a specific user in the target instance — the value is that user's email (must exist in the instance). Resolution order for the effective user: (1) this header, if set; (2) the instance's configured default MCP user; (3) otherwise no user is bound. User-scoped paths (e.g. /users/{email}/…) also accept the literal \"me\", and Graph-style apps serve /me/… twins of those paths (Graph: /me ≡ /users/{signed-in-user}) — both resolve to whatever this header (or the default MCP user) points at. An explicit path email always wins over the header. The unified MCP server does not send this header — it relies on the per-instance default — so it is primarily for direct REST/HTTP callers."}},"schemas":{"TalentReview":{"type":"object","properties":{"id":{"type":"string","readOnly":true,"example":"TR-0001"},"number":{"type":"string","readOnly":true,"example":"TR-0000001"},"name":{"type":"string","example":"Q2 Regional Talent Review"},"state":{"type":"string","enum":["in_preparation","scheduled","in_progress","action_plan","closed"],"description":"Lifecycle (forward-only; SN label 'In Progress' → 'in_progress').","example":"in_progress"},"period_start":{"type":"string","format":"date","example":"2025-04-01"},"period_end":{"type":"string","format":"date","example":"2025-06-30"},"leaders":{"type":"array","description":"Employee IDs leading this review (SN leaders→sys_user).","items":{"type":"string"}},"departments":{"type":"array","description":"Department names in scope.","items":{"type":"string"}},"locations":{"type":"array","description":"Locations in scope.","items":{"type":"string"}},"template_id":{"type":"string","nullable":true},"objectives":{"type":"string","description":"Review objective(s) (e.g. 'Identify retention risks in high-growth hubs')."},"metrics":{"type":"array","description":"Metrics attached to the review.","items":{"type":"object","properties":{"metric_id":{"type":"string","example":"voluntary_attrition_rate"},"group_by":{"type":"string","nullable":true,"example":"department"},"aggregation":{"type":"string","nullable":true,"enum":["count","sum","avg"]},"order":{"type":"integer"},"insights":{"type":"string","nullable":true}}}},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["name"]},"EmployeeAssessment":{"type":"object","properties":{"id":{"type":"string","readOnly":true,"example":"ASSESS-0001"},"employee_id":{"type":"string","example":"EMP-0001"},"talent_review_id":{"type":"string","nullable":true},"assessment_date":{"type":"string","format":"date"},"cycle":{"type":"string","example":"CY2025"},"performance_rating":{"type":"integer","minimum":1,"maximum":5,"description":"1–5 (SN current_performance_level 10–50 / current_rating 30/40/50)."},"performance_band":{"type":"string","enum":["excellent","good","average","needs_improvement","poor"],"description":"Derived band (excellent/good/average/needs_improvement/poor)."},"potential":{"type":"string","enum":["low","medium","high"],"description":"SN 10/20/30 or High/Medium/Low."},"achievable_level":{"type":"string","enum":["1","2","3"],"description":"SN 10/20/30 or '1/2/3 Levels'."},"flight_risk":{"type":"string","enum":["low","medium","high"],"description":"Stored (not derived) per SN assessment."},"replacement_readiness":{"type":"string","enum":["immediate","3m","6m","12m"],"description":"SN 0/3/6/12."},"box":{"type":"integer","minimum":1,"maximum":9,"readOnly":true,"description":"Derived 9-box cell (potential × performance)."},"core_competencies":{"type":"string","nullable":true},"development_needs":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["employee_id"]},"PerformanceReview":{"type":"object","properties":{"id":{"type":"string","readOnly":true,"example":"PR-0001"},"employee_id":{"type":"string","example":"EMP-0001"},"review_start_date":{"type":"string","format":"date"},"review_end_date":{"type":"string","format":"date"},"rating_manager":{"type":"number","minimum":1,"maximum":5,"example":4.2},"rating_employee":{"type":"number","minimum":1,"maximum":5,"example":4},"assessed_on":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["employee_id"]},"SuccessionPlan":{"type":"object","properties":{"id":{"type":"string","readOnly":true,"example":"SP-0001"},"number":{"type":"string","readOnly":true,"example":"SP-0000001"},"title":{"type":"string","example":"Principal Product Manager"},"incumbent_employee_id":{"type":"string","nullable":true,"example":"EMP-0001"},"job_title":{"type":"string","nullable":true},"department":{"type":"string","nullable":true},"planned_retirement_date":{"type":"string","format":"date","nullable":true},"criticality":{"type":"string","enum":["low","medium","high"],"description":"SN demo low/medium/high."},"loss_impact":{"type":"string","enum":["low","medium","high"],"description":"SN High/Intermediate→medium/Low."},"retention_risk":{"type":"string","enum":["low","medium","high"]},"success_profile":{"type":"object","nullable":true,"description":"Role mission/skills/qualifications (SN JSON)."},"successor_count":{"type":"integer","readOnly":true},"external_id":{"type":"string","nullable":true},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["title"]},"Successor":{"type":"object","properties":{"id":{"type":"string","readOnly":true,"example":"SUC-0001"},"succession_plan_id":{"type":"string","example":"SP-0001"},"candidate_employee_id":{"type":"string","example":"EMP-0002"},"top_candidate":{"type":"boolean"},"readiness":{"type":"string","enum":["immediate","3m","6m","12m"],"description":"SN 0/3/6/12 or 'Ready in …' (9 months→6m)."},"potential":{"type":"string","enum":["low","medium","high"]},"achievable_level":{"type":"string","enum":["1","2","3"]},"current_rating":{"type":"string","enum":["excellent","good","average","needs_improvement","poor"],"description":"Banded (SN '4 - Exceeds Expectations')."},"education":{"type":"string","nullable":true},"skills":{"type":"string","nullable":true,"description":"Comma-separated."},"job_history_companies":{"type":"string","nullable":true},"promotion_readiness":{"type":"string","nullable":true},"development_areas":{"type":"string","nullable":true},"matching_score":{"type":"integer","minimum":0,"maximum":100,"nullable":true},"candidate_profile":{"type":"object","nullable":true,"description":"Per-dimension match scores (SN JSON)."},"temporary_fill":{"type":"boolean","nullable":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["succession_plan_id","candidate_employee_id"]},"Snapshot":{"type":"object","properties":{"id":{"type":"string","readOnly":true,"example":"SNP-0001"},"metric_id":{"type":"string","example":"headcount"},"group_id":{"type":"string","nullable":true,"description":"Batch grouping key."},"source":{"type":"string","enum":["local","workday","successfactors","oracle"],"default":"local"},"value":{"type":"object","description":"Computed metric value as JSON (per group)."},"timestamp":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["metric_id","value"]},"MetricGoal":{"type":"object","properties":{"id":{"type":"string","readOnly":true,"example":"GOAL-0001"},"name":{"type":"string","example":"Attrition Rate Goal 2026"},"talent_review_id":{"type":"string","nullable":true},"metric_id":{"type":"string","example":"attrition_rate"},"period_start":{"type":"string","format":"date"},"period_end":{"type":"string","format":"date"},"threshold_value":{"type":"number","example":3},"comparison_type":{"type":"string","enum":["gt","lt","eq","lte","gte"],"example":"lte"},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["metric_id"]},"HrbpScopeAssignment":{"type":"object","properties":{"id":{"type":"string","readOnly":true,"example":"SCOPE-0001"},"user":{"type":"string","description":"User (email/employee id) this scope grants visibility to.","example":"sofia.rossi@acme.com"},"scope_type":{"type":"string","enum":["hrbp","metric_threshold"],"default":"hrbp"},"scope_id":{"type":"string","nullable":true,"description":"Optional link to an HrbpScope (normalized scope definition).","example":"HSCOPE-RND"},"all_access":{"type":"boolean","default":false,"description":"Unrestricted (admin-equivalent) scope — request passes through verbatim."},"leaders":{"type":"array","description":"Leader employee IDs in scope.","items":{"type":"string"}},"departments":{"type":"array","description":"Departments in scope.","items":{"type":"string"}},"locations":{"type":"array","description":"Hierarchical geo scope: region / country / city tokens (a region token covers all its countries & cities).","items":{"type":"string"}},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["user"]},"HrbpScope":{"type":"object","description":"Named HRBP scope (SN sn_talent_pulse_hrbp_scope). Composed of HrbpScopeRule rows; assignments reference it by scope_id.","properties":{"id":{"type":"string","readOnly":true,"example":"HSCOPE-RND"},"name":{"type":"string","example":"R&D — AMS"},"description":{"type":"string","nullable":true},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["name"]},"HrbpScopeRule":{"type":"object","description":"A single dimension rule within an HrbpScope (SN sn_talent_pulse_hrbp_scope_rule).","properties":{"id":{"type":"string","readOnly":true,"example":"HRULE-0001"},"scope_id":{"type":"string","description":"Parent HrbpScope id.","example":"HSCOPE-RND"},"dimension":{"type":"string","enum":["leader","department","location","region","country","city","all_access"],"description":"Geo dimensions (location/region/country/city) all feed the hierarchical location scope.","example":"region"},"value":{"type":"string","nullable":true,"description":"The in-scope value for the dimension (null for all_access). Geo values may be a region (AMS), country (USA), or city (New York, NY).","example":"AMS"},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["scope_id","dimension"]},"SnapshotGroup":{"type":"object","description":"Groups related metric snapshots captured in one run (SN sn_talent_pulse_snapshot_group).","properties":{"id":{"type":"string","readOnly":true,"example":"SGRP-0001"},"name":{"type":"string","example":"March 2026 workforce snapshot"},"captured_at":{"type":"string","format":"date-time"},"source":{"type":"string","enum":["local","workday","successfactors","oracle"],"default":"local"},"note":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["name"]},"MetricSource":{"type":"object","description":"A system a metric value can originate from (SN sn_talent_pulse_metric_source).","properties":{"id":{"type":"string","readOnly":true,"example":"MSRC-local"},"key":{"type":"string","enum":["local","workday","successfactors","oracle"],"example":"local"},"name":{"type":"string","example":"Purple HRIS (local)"},"description":{"type":"string","nullable":true},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["key","name"]},"EmployeeRelation":{"type":"object","description":"Employee Relations case (SN sn_hr_er_case, extends sn_hr_core_case).","properties":{"id":{"type":"string","readOnly":true,"example":"ERC-0001"},"number":{"type":"string","readOnly":true,"example":"ERC-0000001"},"subject_person":{"type":"string","description":"Employee ID the case concerns.","example":"EMP-0003"},"short_description":{"type":"string","example":"Complaint of workplace harassment"},"description":{"type":"string","nullable":true},"state":{"type":"string","enum":["open","work_in_progress","awaiting_info","resolved","closed","cancelled"],"default":"open"},"priority":{"type":"string","enum":["low","moderate","high","critical"],"description":"SN 1-Critical/2-High/3-Moderate/4-Low.","default":"moderate"},"opened_by":{"type":"string","nullable":true,"description":"Employee ID of the HR rep who opened the case."},"assigned_to":{"type":"string","nullable":true,"description":"Employee ID of the assigned HR investigator."},"hr_service":{"type":"string","default":"Employee Relations"},"opened_at":{"type":"string","format":"date-time"},"due_date":{"type":"string","format":"date","nullable":true},"closed_at":{"type":"string","format":"date-time","nullable":true},"active":{"type":"boolean","default":true},"incident_date":{"type":"string","format":"date","nullable":true,"description":"Date of most recent incident."},"external_opened_for":{"type":"boolean","default":false,"description":"Opened for is not a User."},"locked":{"type":"boolean","default":false,"description":"Restricting access limits who can view/edit the case."},"preferred_contact_method":{"type":"string","enum":["not_specified","personal_phone","work_phone","personal_email","work_email"],"default":"not_specified"},"reported_date":{"type":"string","format":"date-time","nullable":true},"pip_start_date":{"type":"string","format":"date","nullable":true},"pip_end_date":{"type":"string","format":"date","nullable":true},"pip_improvement_areas":{"type":"string","nullable":true},"pip_rationale":{"type":"string","nullable":true},"pip_successful":{"type":"string","enum":["y","n"],"nullable":true},"pip_extended":{"type":"string","enum":["y","n"],"nullable":true},"pip_extension_date":{"type":"string","format":"date","nullable":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["subject_person","short_description"]},"AllegationType":{"type":"object","description":"Allegation type reference catalog (SN sn_hr_er_allegation_type).","properties":{"id":{"type":"string","readOnly":true,"example":"AT-HARASSMENT"},"name":{"type":"string","example":"Harassment"},"description":{"type":"string","nullable":true},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["name"]},"AllegationSubtype":{"type":"object","description":"Allegation subtype, dependent on allegation_type (SN sn_hr_er_allegation_subtype).","properties":{"id":{"type":"string","readOnly":true,"example":"AST-VERBAL"},"allegation_type_id":{"type":"string","example":"AT-HARASSMENT"},"name":{"type":"string","example":"Verbal harassment"},"active":{"type":"boolean","default":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["allegation_type_id","name"]},"Allegation":{"type":"object","description":"One allegation filed on an ER case (SN sn_hr_er_allegation).","properties":{"id":{"type":"string","readOnly":true,"example":"ALG-0001"},"employee_relation_id":{"type":"string","example":"ERC-0001"},"allegation_type_id":{"type":"string","example":"AT-HARASSMENT"},"allegation_subtype_id":{"type":"string","example":"AST-VERBAL"},"description":{"type":"string","nullable":true},"subjects_of_allegation":{"type":"string","readOnly":true,"nullable":true,"description":"Derived: names of the case's 'respondent' involved parties."},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["employee_relation_id","allegation_type_id","allegation_subtype_id"]},"InvolvedParty":{"type":"object","description":"A complainant, respondent, witness, or other party on an ER case (SN sn_hr_er_involved_party).","properties":{"id":{"type":"string","readOnly":true,"example":"IP-0001"},"employee_relation_id":{"type":"string","example":"ERC-0001"},"employee_id":{"type":"string","nullable":true,"description":"Employee ID, if the party has a User record.","example":"EMP-0008"},"not_in_system":{"type":"boolean","default":false,"description":"Involved party does not have a User record."},"name":{"type":"string","nullable":true,"description":"Name/description, used when not_in_system (or no employee_id)."},"display_name":{"type":"string","readOnly":true,"description":"Derived: the linked employee's name, else name."},"type":{"type":"string","enum":["complainant","respondent","witness","other"],"default":"other"},"correlation_id":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}},"required":["employee_relation_id","type"]}}},"paths":{"/resolve_filters":{"post":{"summary":"Resolve & validate HRBP scope filters (tp_resolve_scope)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Resolve & validate HRBP scope filters (tp_resolve_scope)"},"400":{"description":"Bad request"}}}},"/available_metrics":{"get":{"summary":"List predefined talent metrics (tp_list_metrics)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"List predefined talent metrics (tp_list_metrics)"},"400":{"description":"Bad request"}}}},"/available_metric_categories":{"get":{"summary":"List metric categories","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"List metric categories"},"400":{"description":"Bad request"}}}},"/execute_known_metric":{"post":{"summary":"Compute one predefined metric (tp_execute_metric)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Compute one predefined metric (tp_execute_metric)"},"400":{"description":"Bad request"}}}},"/find_matching_reviews":{"post":{"summary":"Find talent reviews by filters (tp_find_reviews)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Find talent reviews by filters (tp_find_reviews)"},"400":{"description":"Bad request"}}}},"/preview_review":{"post":{"summary":"Preview a talent review (computes metrics); mints idempotency_key (tp_preview_review)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Preview a talent review (computes metrics); mints idempotency_key (tp_preview_review)"},"400":{"description":"Bad request"}}}},"/commit_review":{"post":{"summary":"Commit a previewed talent review (tp_commit_review)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Commit a previewed talent review (tp_commit_review)"},"400":{"description":"Bad request"}}}},"/add_metric_to_review":{"post":{"summary":"Add a metric to an existing talent review (tp_add_metric)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Add a metric to an existing talent review (tp_add_metric)"},"400":{"description":"Bad request"}}}},"/display_talent_review":{"post":{"summary":"Display a talent review + computed metrics (tp_display_review)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Display a talent review + computed metrics (tp_display_review)"},"400":{"description":"Bad request"}}}},"/hrbp_data_analytics/query":{"post":{"summary":"HRBP data-analytics query — NL question → generated query, aggregates, inline insights, and async answer/follow-up logIds (SN hrbp_data_analytics/query). Returns 202 on submission; poll result.analysis.logId / result.followUps.logId via /hrbp_data_analytics/async_results.","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"method","required":false,"schema":{"type":"string","enum":["fullPipeline","insightsOnly","custom","getAsyncResults"],"default":"fullPipeline"},"description":"fullPipeline needs {utterance, channelId}; insightsOnly needs {channelId} plus dataConfigurations or dataResponses; custom needs {featureFlags}; getAsyncResults needs {logId}."},{"in":"query","name":"seed","required":false,"schema":{"type":"string"},"description":"Pin the simulated model output so a run is reproducible. Omit for fresh phrasing on every call (the underlying numbers never change)."},{"in":"query","name":"readyDelayMs","required":false,"schema":{"type":"integer","default":1500,"minimum":0,"maximum":60000},"description":"How long the async answer/follow-up capabilities stay in `processing` before results land. 0 makes them readable immediately."}],"responses":{"202":{"description":"Accepted — async capabilities submitted"},"400":{"description":"Validation error (missing required field)"},"404":{"description":"getAsyncResults: unknown logId"}}}},"/hrbp_data_analytics/async_results":{"get":{"summary":"Batch-poll async analytics results by logId(s) (SN hrbp_data_analytics/async_results)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"logIds","required":false,"schema":{"type":"string"},"description":"Comma-separated log ids."},{"in":"query","name":"logId","required":false,"schema":{"type":"string"},"description":"A single log id (combinable with logIds)."}],"responses":{"200":{"description":"Batch-poll async analytics results by logId(s) (SN hrbp_data_analytics/async_results)"},"400":{"description":"Bad request"}}}},"/hrbp_action_recommendations_rest_api/recommendations":{"post":{"summary":"Generate action recommendations from an analytics finding — accepts {goal, content} or {goal, queryGenResponse|fullpipeline_response} (SN hrbp_action_recommendations_rest_api/recommendations)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Generate action recommendations from an analytics finding — accepts {goal, content} or {goal, queryGenResponse|fullpipeline_response} (SN hrbp_action_recommendations_rest_api/recommendations)"},"400":{"description":"Bad request"}}}},"/query":{"post":{"summary":"Alias of /hrbp_data_analytics/query (pre-existing Purple path)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Alias of /hrbp_data_analytics/query (pre-existing Purple path)"},"400":{"description":"Bad request"}}}},"/async_results":{"get":{"summary":"Alias of /hrbp_data_analytics/async_results (pre-existing Purple path)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Alias of /hrbp_data_analytics/async_results (pre-existing Purple path)"},"400":{"description":"Bad request"}}}},"/recommendations":{"post":{"summary":"Alias of /hrbp_action_recommendations_rest_api/recommendations (pre-existing Purple path)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Alias of /hrbp_action_recommendations_rest_api/recommendations (pre-existing Purple path)"},"400":{"description":"Bad request"}}}},"/fuzzy_value_resolver":{"get":{"summary":"Resolve a raw value to catalog {value, sys_id} matches via exact->startswith->contains->fuzzy->text cascade (SN FuzzyValueResolver parity)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"tableName","required":true,"schema":{"type":"string"}},{"in":"query","name":"columnName","required":true,"schema":{"type":"string"}},{"in":"query","name":"rawValue","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Resolve a raw value to catalog {value, sys_id} matches via exact->startswith->contains->fuzzy->text cascade (SN FuzzyValueResolver parity)"},"400":{"description":"Bad request"}}}},"/available_templates":{"get":{"summary":"List talent-review templates","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"List talent-review templates"},"400":{"description":"Bad request"}}}},"/create_review_from_template":{"post":{"summary":"Create a talent review from a template (direct write)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Create a talent review from a template (direct write)"},"400":{"description":"Bad request"}}}},"/search_talent_reviews":{"post":{"summary":"Search talent reviews by filter names + scope; returns templates","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Search talent reviews by filter names + scope; returns templates"},"400":{"description":"Bad request"}}}},"/execute_talent_review":{"post":{"summary":"Execute a talent review's stored metrics by id","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Execute a talent review's stored metrics by id"},"400":{"description":"Bad request"}}}},"/query_metric":{"post":{"summary":"Resolve filters + compute one metric widget","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Resolve filters + compute one metric widget"},"400":{"description":"Bad request"}}}},"/hr_program/summary":{"get":{"summary":"Get an HR program summary by name keyword (q)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Get an HR program summary by name keyword (q)"},"400":{"description":"Bad request"}}}},"/hr_program/draft_notification":{"post":{"summary":"Raw program data + stakeholder contacts for an LLM to draft an HR program notification from (no server-built draft text)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Raw program data + stakeholder contacts for an LLM to draft an HR program notification from (no server-built draft text)"},"400":{"description":"Bad request"}}}},"/hr_program/get_draft":{"post":{"summary":"Retrieve the caller's persisted notification draft HTML for a program","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Retrieve the caller's persisted notification draft HTML for a program"},"400":{"description":"Bad request"}}}},"/hr_program/save_draft":{"post":{"summary":"Persist the caller's notification draft HTML for a program","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Persist the caller's notification draft HTML for a program"},"400":{"description":"Bad request"}}}},"/hr_program/program_details/{sys_id_csv}":{"get":{"summary":"Full nested detail (fields, program type, milestones, resources, stakeholders, action plans) for one or more programs by comma-separated sys_id","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"sys_id_csv","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Full nested detail (fields, program type, milestones, resources, stakeholders, action plans) for one or more programs by comma-separated sys_id"},"400":{"description":"Bad request"}}}},"/hr_program":{"get":{"summary":"List the caller's stakeholder/owner programs, grouped by state with upcoming milestones","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"List the caller's stakeholder/owner programs, grouped by state with upcoming milestones"},"400":{"description":"Bad request"}}}},"/hr_profile/new_hire":{"get":{"summary":"New hires within the calling HRBP's scope for a hire-date window (start_date/end_date, yyyy-MM-dd; defaults to the last 30 days). Returns { start_date, end_date, count, employees:[{ name, hire_date, days_since_hire, position, department, manager }] }, wrapped in {result:...} (SN HRBPNewHireDetailsSNC parity).","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"start_date","required":false,"schema":{"type":"string"}},{"in":"query","name":"end_date","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"New hires within the calling HRBP's scope for a hire-date window (start_date/end_date, yyyy-MM-dd; defaults to the last 30 days). Returns { start_date, end_date, count, employees:[{ name, hire_date, days_since_hire, position, department, manager }] }, wrapped in {result:...} (SN HRBPNewHireDetailsSNC parity)."},"400":{"description":"Bad request"}}}},"/employee/{sys_id}/work_history":{"get":{"summary":"An employee's job/position history (tenure duration + current-job status), most recent first, by employee sys_id. NOT self-scoped — any internal-role caller may look up any employee. Wraps both success AND error bodies in {result:...} (its SN op routes every 200/400/500 path through response.setBody(), same precedent as /hr_profile/new_hire).","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"sys_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"An employee's job/position history (tenure duration + current-job status), most recent first, by employee sys_id. NOT self-scoped — any internal-role caller may look up any employee. Wraps both success AND error bodies in {result:...} (its SN op routes every 200/400/500 path through response.setBody(), same precedent as /hr_profile/new_hire)."},"400":{"description":"Bad request"}}}},"/hr_case":{"get":{"summary":"List the caller's HRBP cases (paginated, AI-enriched)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"List the caller's HRBP cases (paginated, AI-enriched)"},"400":{"description":"Bad request"}}}},"/hr_case/{sys_id}":{"get":{"summary":"Get full HR case detail + AI enrichment + approvers (with raw_state) + collaborators + meeting_attendees + pending-approval flag","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"sys_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Get full HR case detail + AI enrichment + approvers (with raw_state) + collaborators + meeting_attendees + pending-approval flag"},"400":{"description":"Bad request"}}}},"/hr_case/{sys_id}/approval":{"post":{"summary":"Approve or reject the caller's pending approval on an HR case","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"sys_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Approve or reject the caller's pending approval on an HR case"},"400":{"description":"Bad request"}}}},"/api/now/table/sn_hr_core_case":{"get":{"summary":"ServiceNow Table API parity: look up a case by number (sysparm_query=number=<value>); wraps in {result: [...]}","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"ServiceNow Table API parity: look up a case by number (sysparm_query=number=<value>); wraps in {result: [...]}"},"400":{"description":"Bad request"}}}},"/api/now/table/sn_hr_core_case/{sys_id}":{"patch":{"summary":"ServiceNow Table API parity: update a case's state (+ optional work_notes) for defer(24)/resume(18)/cancel(7); wraps in {result: {...}}","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"sys_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ServiceNow Table API parity: update a case's state (+ optional work_notes) for defer(24)/resume(18)/cancel(7); wraps in {result: {...}}"},"400":{"description":"Bad request"}}}},"/api/now/table/sys_user":{"get":{"summary":"ServiceNow Table API parity: sys_user lookup (sysparm_query=nameLIKE<term>) for the u_SysUser slot resolver; wraps in {result: [...]}","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"sysparm_query","required":false,"schema":{"type":"string"},"example":"nameLIKETaco"},{"in":"query","name":"sysparm_fields","required":false,"schema":{"type":"string"},"example":"sys_id,name,email"},{"in":"query","name":"sysparm_limit","required":false,"schema":{"type":"integer"},"example":10},{"in":"query","name":"sysparm_offset","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"ServiceNow Table API parity: sys_user lookup (sysparm_query=nameLIKE<term>) for the u_SysUser slot resolver; wraps in {result: [...]}"},"400":{"description":"Bad request"}}}},"/api/now/table/sn_hr_core_employee_certification_and_license":{"get":{"summary":"ServiceNow Table API parity: an employee's certification/license records (sysparm_query=employee=<sys_id>^active=true). Supports the dot-walked certification_or_license.governing_agency field via sysparm_fields; wraps in {result: [...]}. Always shaped as sysparm_display_value=true + sysparm_exclude_reference_link=true (the emulated action sends both as true; not a toggle here — see README).","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"sysparm_query","required":false,"schema":{"type":"string"},"example":"employee=12cf2eef933d0390f657fdab3603d6b3^active=true"},{"in":"query","name":"sysparm_fields","required":false,"schema":{"type":"string"},"example":"certification_or_license,certification_or_license.governing_agency,issue_date,expiration_date,status,employee,sys_id"},{"in":"query","name":"sysparm_limit","required":false,"schema":{"type":"integer"}},{"in":"query","name":"sysparm_offset","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"ServiceNow Table API parity: an employee's certification/license records (sysparm_query=employee=<sys_id>^active=true). Supports the dot-walked certification_or_license.governing_agency field via sysparm_fields; wraps in {result: [...]}. Always shaped as sysparm_display_value=true + sysparm_exclude_reference_link=true (the emulated action sends both as true; not a toggle here — see README)."},"400":{"description":"Bad request"}}}},"/api/now/table/sn_hr_core_profile":{"get":{"summary":"ServiceNow Table API parity: look up an employee's HR profile by name (sysparm_query=user.name=<value>); reference fields (user.department/user.manager/user.location) as {display_value, link}; wraps in {result: [...]}","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"ServiceNow Table API parity: look up an employee's HR profile by name (sysparm_query=user.name=<value>); reference fields (user.department/user.manager/user.location) as {display_value, link}; wraps in {result: [...]}"},"400":{"description":"Bad request"}}}},"/api/now/table/sys_user_has_skill":{"get":{"summary":"ServiceNow Table API parity: list an employee's skills by sys_id (sysparm_query=user.sys_id=<value>); skill/skill_level as {display_value, link} (skill_level is \"\" when unset); wraps in {result: [...]}","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"ServiceNow Table API parity: list an employee's skills by sys_id (sysparm_query=user.sys_id=<value>); skill/skill_level as {display_value, link} (skill_level is \"\" when unset); wraps in {result: [...]}"},"400":{"description":"Bad request"}}}},"/er_cases":{"get":{"summary":"Look up an ER case by number, or count ER cases by allegation type / department / location / leader / hr_service / date range (SN HRERCaseAPI parity)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Look up an ER case by number, or count ER cases by allegation type / department / location / leader / hr_service / date range (SN HRERCaseAPI parity)"},"400":{"description":"Bad request"}}}},"/hrbp_batch_user_resolution/resolve":{"get":{"summary":"Batch resolve user names to emails for meeting scheduling (SN hrbp_batch_user_resolution parity)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Batch resolve user names to emails for meeting scheduling (SN hrbp_batch_user_resolution parity)"},"400":{"description":"Bad request"}}}},"/hrbp_capabilities":{"get":{"summary":"Which optional HRBP Hub modules (e.g. Talent Pulse) are available on this instance (SN hrbp_capabilities parity)","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Which optional HRBP Hub modules (e.g. Talent Pulse) are available on this instance (SN hrbp_capabilities parity)"},"400":{"description":"Bad request"}}}},"/hrbp_manager_span":{"get":{"summary":"Manager span of control. With manager_sys_id: that manager's direct + total (transitive) report counts — {manager_sys_id, num_direct_reports, num_total_reports}, 404 if no such user, zeroes if they are not an active manager. Without it: the CALLING HRBP's in-scope managers ranked by total reports, paginated 10 per page — {page_number, count, managers, average_total_reports} (count is this page's size); never accepts a caller-supplied hrbpSysId. Wraps success AND error bodies in {result:...} (every branch of the SN op routes through response.setBody()).","x-mcp":false,"x-odata":false,"parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"manager_sys_id","required":false,"schema":{"type":"string"},"example":"EMP-0003"},{"in":"query","name":"page_number","required":false,"schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Manager span of control. With manager_sys_id: that manager's direct + total (transitive) report counts — {manager_sys_id, num_direct_reports, num_total_reports}, 404 if no such user, zeroes if they are not an active manager. Without it: the CALLING HRBP's in-scope managers ranked by total reports, paginated 10 per page — {page_number, count, managers, average_total_reports} (count is this page's size); never accepts a caller-supplied hrbpSysId. Wraps success AND error bodies in {result:...} (every branch of the SN op routes through response.setBody())."},"400":{"description":"Bad request"}}}},"/talent_reviews":{"get":{"summary":"List talent reviews","description":"A talent review is the HRBP's saved analytics package: a named review over a scope (leaders / departments / locations) and period, carrying an ordered list of attached metrics (headcount, attrition_rate, 9-box distribution, ...). It is the record the tp_* ServiceNow-parity tools create and execute. Use this raw list to browse or audit review records; use tp_find_reviews / tp_search_reviews when resolving by leader or department NAME. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TalentReview"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create a talent review record directly","description":"Writes a talent review row with its scope, period and metric list, bypassing the guided flow. Prefer tp_preview_review + tp_commit_review (or tp_create_review_from_template), which validate the HRBP's scope access and de-duplicate via an idempotency key.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TalentReview"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TalentReview"}}}}}}},"/talent_reviews/{id}":{"get":{"summary":"Get a talent review by id","description":"Returns one review's definition - scope, period and attached metrics - but NOT computed results. Use tp_display_review or tp_execute_review to get metric values.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TalentReview"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit a talent review record","description":"Partial update of a review's name, scope, period or attached metric list. To add a metric to an existing review prefer tp_add_metric, which is idempotent on (review, metric).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TalentReview"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TalentReview"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a talent review","description":"DESTRUCTIVE and irreversible: removes the review and its attached metric configuration. Any snapshots already captured from it survive as separate records. Confirm with the user before calling.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/employee_assessments":{"get":{"summary":"List 9-box employee assessments","description":"An employee assessment is one employee's talent-review datapoint for a cycle: performance_rating (1-5) and derived performance_band, potential, flight_risk, replacement_readiness, achievable_level and the derived 9-box cell. This is HRBP calibration data, distinct from HRIS performance reviews (the formal review record) and from performance_reviews here (manager/employee rating pairs). Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmployeeAssessment"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create an employee assessment (9-box datapoint)","description":"Records one employee's calibration for a cycle, optionally tied to a talent_review_id. `box` is derived from potential x performance - do not set it.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeAssessment"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeAssessment"}}}}}}},"/employee_assessments/{id}":{"get":{"summary":"Get an employee assessment by id","description":"Returns one employee's assessment for a cycle, including the derived 9-box cell, core competencies and development needs.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeAssessment"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit an employee assessment","description":"Partial update of a calibration datapoint (rating, potential, flight risk, readiness). The 9-box cell is recomputed by the server.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeAssessment"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeAssessment"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete an employee assessment","description":"DESTRUCTIVE and irreversible: removes the employee's calibration datapoint for that cycle, changing any 9-box or performance-distribution metric computed afterwards.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/performance_reviews":{"get":{"summary":"List performance review ratings","description":"A performance review here is a lightweight rating pair for one employee over a review window: rating_manager and rating_employee (1-5) plus the assessment date. It feeds HRBP performance metrics. Narrower than an HRIS employee review (strengths/gaps narrative) and than an employee assessment (9-box potential and flight risk). Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PerformanceReview"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create a performance review rating","description":"Records a manager and/or self rating for an employee over a review period. Use employee_assessments instead when you need potential, flight risk or 9-box placement.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerformanceReview"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerformanceReview"}}}}}}},"/performance_reviews/{id}":{"get":{"summary":"Get a performance review rating by id","description":"Returns one rating record: the employee, the review window and the manager/employee scores.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerformanceReview"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit a performance review rating","description":"Partial update of the review window or the manager/employee ratings.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerformanceReview"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerformanceReview"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a performance review rating","description":"DESTRUCTIVE and irreversible: removes the rating record, which changes any performance metric recomputed afterwards.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/succession_plans":{"get":{"summary":"List succession plans for critical roles","description":"A succession plan covers one critical ROLE (title, incumbent, department) with its criticality, loss_impact, retention_risk, success profile and a count of named successors. The candidates themselves are separate successor records linked by succession_plan_id. Use this to answer 'which roles have succession coverage'. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SuccessionPlan"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create a succession plan for a role","description":"Opens a plan for a critical role and its incumbent. Add candidates afterwards as successor records; successor_count is maintained by the server.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessionPlan"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessionPlan"}}}}}}},"/succession_plans/{id}":{"get":{"summary":"Get a succession plan by id","description":"Returns one role's plan - incumbent, criticality, loss impact, retention risk and success profile. Candidate details live in the successors collection filtered by succession_plan_id.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessionPlan"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit a succession plan","description":"Partial update of the plan's role metadata, risk ratings or success profile. Adding or ranking candidates is done on successor records, not here.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessionPlan"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessionPlan"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a succession plan","description":"DESTRUCTIVE and irreversible: removes the plan. Its successor records are orphaned rather than deleted. Prefer setting `active` to false to retire a plan.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/successors":{"get":{"summary":"List succession candidates","description":"A successor is one named candidate on a succession plan: candidate_employee_id, top_candidate flag, readiness (ready now / 3 / 6 / 12 months), potential, current rating, matching score and development areas. Filter by succession_plan_id to see a role's bench; filter by candidate_employee_id to see which roles a person is slated for. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Successor"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Add a candidate to a succession plan","description":"Names an employee as a successor for the plan given by succession_plan_id, with their readiness and potential. The plan's successor_count is updated by the server.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Successor"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Successor"}}}}}}},"/successors/{id}":{"get":{"summary":"Get a succession candidate by id","description":"Returns one candidate's bench entry: readiness, potential, achievable level, match score and development areas.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Successor"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit a succession candidate","description":"Partial update of a candidate's readiness, potential, top_candidate flag or development areas - the usual way to re-rank a bench after calibration.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Successor"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Successor"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Remove a candidate from a succession plan","description":"DESTRUCTIVE and irreversible: deletes the bench entry and lowers the plan's coverage. The employee record itself is untouched.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/snapshots":{"get":{"summary":"List captured metric snapshots","description":"A snapshot is one metric value frozen at a point in time: metric_id, the computed value JSON (per group), a source and a timestamp, batched under a group_id. Snapshots are the history that makes trend questions answerable; to compute a metric live use tp_execute_metric or tp_query_metric instead. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Snapshot"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Capture a metric snapshot","description":"Stores a computed metric value against a metric_id and group_id. Normally written by the snapshot pipeline - creating one by hand inserts a datapoint into the trend history.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}}}}},"/snapshots/{id}":{"get":{"summary":"Get a metric snapshot by id","description":"Returns one frozen metric value with its timestamp, source and per-group breakdown.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit a metric snapshot","description":"Partial update of a stored datapoint. Editing history rewrites what trend queries will report - prefer capturing a new snapshot.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a metric snapshot","description":"DESTRUCTIVE and irreversible: removes a datapoint from the trend history, which changes previously reported trends.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/metric_goals":{"get":{"summary":"List metric goals (targets and thresholds)","description":"A metric goal is a target set on a metric for a period: metric_id, period_start/end, threshold_value and a comparison_type saying whether being above or below the threshold is good. Optionally scoped to a talent review. Read these to judge whether a computed metric is on or off target. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MetricGoal"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create a metric goal","description":"Sets a target threshold for a metric over a period (e.g. attrition_rate below 3% for FY26), optionally tied to a talent review. Defines the bar; it does not compute anything.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricGoal"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricGoal"}}}}}}},"/metric_goals/{id}":{"get":{"summary":"Get a metric goal by id","description":"Returns one target: its metric, period, threshold value and comparison direction.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricGoal"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit a metric goal","description":"Partial update of a target's threshold, period or comparison direction - i.e. moving the bar.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricGoal"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricGoal"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a metric goal","description":"DESTRUCTIVE and irreversible: removes the target, after which the metric has no threshold to be judged against. Prefer setting `active` to false.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/hrbp_scope_assignments":{"get":{"summary":"List HRBP scope assignments (who can see what)","description":"A scope assignment grants ONE HRBP user access to one scope - by scope_type/scope_id, or all_access for an unrestricted HR leader. This is the access-control table that tp_resolve_scope checks before any metric or review runs, and it is why a query can come back access_denied. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/HrbpScopeAssignment"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Grant an HRBP access to a scope","description":"Assigns a user a scope (or all_access). SECURITY-SENSITIVE: this widens who can see employee talent data. Confirm with the user before calling.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeAssignment"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeAssignment"}}}}}}},"/hrbp_scope_assignments/{id}":{"get":{"summary":"Get an HRBP scope assignment by id","description":"Returns one grant: the user, the scope type/id it covers, whether it is all_access, and whether it is active.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeAssignment"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit an HRBP scope assignment","description":"Partial update of a grant (repoint the scope, toggle all_access or active). SECURITY-SENSITIVE - it changes what employee data that HRBP can see.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeAssignment"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeAssignment"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Revoke an HRBP scope assignment","description":"DESTRUCTIVE and irreversible: removes the grant, so that HRBP loses access to the scope and their queries may start returning access_denied.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/hrbp_scopes":{"get":{"summary":"List HRBP scopes (named population definitions)","description":"An HRBP scope is a NAMED population of employees (e.g. 'R&D - AMS') whose membership is defined by its hrbp_scope_rules. Scopes are what scope assignments hand out to HRBP users. Use this to see which populations exist before assigning one. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/HrbpScope"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create an HRBP scope","description":"Defines a new named population. It matches nobody until you add hrbp_scope_rules to it.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScope"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScope"}}}}}}},"/hrbp_scopes/{id}":{"get":{"summary":"Get an HRBP scope by id","description":"Returns one scope's name, description and active flag. Its membership criteria are the hrbp_scope_rules with this scope_id.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScope"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit an HRBP scope","description":"Partial update of a scope's name, description or active flag. Membership is changed by editing its rules, not here.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScope"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScope"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete an HRBP scope","description":"DESTRUCTIVE and irreversible: removes the population definition. Its rules and any assignments pointing at it are left dangling, so HRBPs may lose access. Confirm with the user before calling.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/hrbp_scope_rules":{"get":{"summary":"List HRBP scope membership rules","description":"A scope rule is one membership clause of an HRBP scope: a dimension (department, location, leader, ...) and the value that must match. Rules combine to define who the parent scope covers. Filter by scope_id to read one population's definition. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/HrbpScopeRule"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Add a membership rule to an HRBP scope","description":"Adds a dimension/value clause to the scope given by scope_id, widening or narrowing who that population covers. SECURITY-SENSITIVE: it changes what data the scope's HRBPs can reach.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeRule"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeRule"}}}}}}},"/hrbp_scope_rules/{id}":{"get":{"summary":"Get an HRBP scope rule by id","description":"Returns one membership clause: its parent scope, the dimension it matches on, and the value.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeRule"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit an HRBP scope rule","description":"Partial update of a clause's dimension, value or active flag. SECURITY-SENSITIVE - it silently changes the population an HRBP can see.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeRule"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HrbpScopeRule"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete an HRBP scope rule","description":"DESTRUCTIVE and irreversible: removes a membership clause, shrinking (or, if it was the last exclusion, widening) the parent scope.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/snapshot_groups":{"get":{"summary":"List snapshot capture batches","description":"A snapshot group is one capture run - a named, timestamped batch (e.g. 'March 2026 workforce snapshot') that the individual snapshot records hang off via group_id. Use this to find which capture runs exist, then read snapshots for the values. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SnapshotGroup"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create a snapshot capture batch","description":"Opens a named, timestamped batch that snapshot records can be filed under. It captures no metrics by itself.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotGroup"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotGroup"}}}}}}},"/snapshot_groups/{id}":{"get":{"summary":"Get a snapshot capture batch by id","description":"Returns one batch's name, capture timestamp, source and note. The metric values are the snapshots carrying this group_id.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotGroup"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit a snapshot capture batch","description":"Partial update of a batch's name, note or source. Does not change the snapshot values inside it.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotGroup"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotGroup"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a snapshot capture batch","description":"DESTRUCTIVE and irreversible: removes the batch header. Its snapshots survive but lose their grouping.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/metric_sources":{"get":{"summary":"List metric data sources","description":"A metric source is a registered system that talent metrics can be computed from (e.g. 'local' = Purple HRIS). Small reference table: it records where numbers come from, not the numbers themselves. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MetricSource"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Register a metric data source","description":"Adds a source system that snapshots and metric computations can attribute to. Administrative configuration.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricSource"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricSource"}}}}}}},"/metric_sources/{id}":{"get":{"summary":"Get a metric data source by id","description":"Returns one registered source: its key, display name and active flag.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricSource"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit a metric data source","description":"Partial update of a source's name, description or active flag.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricSource"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricSource"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a metric data source","description":"DESTRUCTIVE and irreversible: removes the source registration. Snapshots that referenced it keep the key but it no longer resolves.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/employee_relations":{"get":{"summary":"List employee relations (ER) cases","description":"An ER case is an HR investigation or performance-management record about one subject_person: short description, state, priority, the HR rep who opened it and the investigator assigned, due/closed dates, plus PIP fields (start/end, improvement areas, outcome). SENSITIVE HR data, and separate from the talent-review side of this app. Allegations and involved parties are child records keyed by employee_relation_id. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, ... because $ is not a legal tool-input key).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmployeeRelation"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Open an employee relations (ER) case","description":"Creates an investigation / performance-management case about an employee. SENSITIVE: it starts a formal HR record about a named person - confirm with the user before calling.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeRelation"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeRelation"}}}}}}},"/employee_relations/{id}":{"get":{"summary":"Get an employee relations (ER) case by id","description":"Returns one case with its state, priority, assignment, dates and PIP details. SENSITIVE HR content; a locked case restricts who may read it.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeRelation"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Edit an employee relations (ER) case","description":"Partial update of a case - reassign the investigator, change state or priority, close it, or record PIP progress and outcome. SENSITIVE HR record.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeRelation"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeeRelation"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete an employee relations (ER) case","description":"DESTRUCTIVE and irreversible: erases the investigation record and its audit trail, and orphans its allegations and involved parties. Prefer closing the case (state) over deleting it.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/allegation_types":{"get":{"summary":"List allegation types","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AllegationType"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create allegation type","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationType"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationType"}}}}}}},"/allegation_types/{id}":{"get":{"summary":"Get allegation type","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationType"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update allegation type","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationType"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationType"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete allegation type","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/allegation_subtypes":{"get":{"summary":"List allegation subtypes","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AllegationSubtype"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create allegation subtype","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationSubtype"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationSubtype"}}}}}}},"/allegation_subtypes/{id}":{"get":{"summary":"Get allegation subtype","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationSubtype"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update allegation subtype","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationSubtype"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllegationSubtype"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete allegation subtype","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/allegations":{"get":{"summary":"List allegations","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Allegation"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create allegation","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Allegation"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Allegation"}}}}}}},"/allegations/{id}":{"get":{"summary":"Get allegation","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Allegation"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update allegation","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Allegation"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Allegation"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete allegation","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/involved_parties":{"get":{"summary":"List involved parties","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InvolvedParty"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create involved party","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvolvedParty"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvolvedParty"}}}}}}},"/involved_parties/{id}":{"get":{"summary":"Get involved party","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvolvedParty"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update involved party","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvolvedParty"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvolvedParty"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete involved party","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}}}}