---
**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`.
---
**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`.
Operations
POST /employees/search
Search HRIS employees by name, email or attributes
Fuzzy person lookup over the HRIS employee master (the record of employment: manager, department, title, salary band, status) - not the identity directory or a chat user roster. Pass `query` for scored free-text search OR framed fields (name, email, department, role, manager_name, employment_status) for structured search; the two are mutually exclusive. Returns RecordResult envelopes with a nested person_entity. Reach for this when you have a partial or misspelled name; use the employees list when you can express the criteria as an exact $filter.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Search HRIS employees by name, email or attributes",
"description": "Fuzzy person lookup over the HRIS employee master (the record of employment: manager, department, title, salary band, status) - not the identity directory or a chat user roster. Pass `query` for scored free-text search OR framed fields (name, email, department, role, manager_name, employment_status) for structured search; the two are mutually exclusive. Returns RecordResult envelopes with a nested person_entity. Reach for this when you have a partial or misspelled name; use the employees list when you can express the criteria as an exact $filter.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Free-text search query (tokenized, scored by term overlap). Mutually exclusive with framed fields.",
"example": "Alice Engineering"
},
"name": {
"type": "string",
"description": "Full or partial person name — firstName + lastName substring match (framed search)",
"example": "Alice Smith"
},
"email": {
"type": "string",
"description": "Email address partial match (framed search)",
"example": "alice.smith"
},
"department": {
"type": "string",
"description": "Exact department name match (framed search)",
"example": "Engineering"
},
"role": {
"type": "string",
"description": "Role/title substring match (framed search)",
"example": "Engineer"
},
"manager_name": {
"type": "string",
"description": "Manager full name substring — applied as post-filter after framed scoring (framed search)",
"example": "Bob Johnson"
},
"manager_email": {
"type": "string",
"description": "Manager email substring — applied as post-filter after framed scoring (framed search)",
"example": "bob.johnson"
},
"employment_status": {
"type": "string",
"enum": [
"active",
"terminated",
"on_leave",
"suspended"
],
"description": "Employment status exact match (framed search)",
"example": "active"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Search results in RecordResult format (top 5 matches by score)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"record": {
"type": "object",
"description": "Nested RecordResult envelope",
"properties": {
"meta_info": {
"type": "object",
"properties": {
"structured_entity": {
"type": "object",
"properties": {
"person_entity": {
"type": "object",
"properties": {
"person_info": {
"type": "object",
"properties": {
"full_name": {
"type": "string"
},
"employee_id": {
"type": "string"
}
}
},
"contact_info": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"phone_number": {
"type": "string",
"nullable": true
},
"aliases": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"occupation_info": {
"type": "object",
"properties": {
"role": {
"type": "string"
},
"manager": {
"type": "object",
"nullable": true,
"properties": {
"full_name": {
"type": "string"
},
"email": {
"type": "string"
}
}
},
"direct_reports": {
"type": "array",
"items": {
"type": "object"
}
}
}
},
"workplace_info": {
"type": "object",
"properties": {
"department": {
"type": "string"
},
"timezone": {
"type": "string",
"nullable": true
},
"location": {
"type": "string",
"nullable": true
},
"cost_center": {
"type": "string",
"nullable": true
}
}
}
}
}
}
}
}
}
}
},
"score": {
"type": "number",
"description": "Match score between 0 and 1"
},
"confidence": {
"type": "string",
"enum": [
"EXACT_MATCH",
"VALID",
"INVALID"
],
"description": "EXACT_MATCH ≥ 0.9, VALID ≥ 0.5, INVALID < 0.5"
}
}
}
},
"total_retrieved": {
"type": "integer"
}
}
}
}
}
},
"500": {
"description": "Internal server error"
}
}
}
POST /users/search
Search HRIS employees, projected as flat User records
Identical search semantics to the employee search (free-text `query` OR framed fields) but each match comes back as a flat User-shaped object (email_addr, full_name, record_id, ...) instead of the nested RecordResult envelope. Choose this only when the caller expects native-User field names; otherwise prefer the employee search. It still reads the HRIS employee master, not an identity or chat directory.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Search HRIS employees, projected as flat User records",
"description": "Identical search semantics to the employee search (free-text `query` OR framed fields) but each match comes back as a flat User-shaped object (email_addr, full_name, record_id, ...) instead of the nested RecordResult envelope. Choose this only when the caller expects native-User field names; otherwise prefer the employee search. It still reads the HRIS employee master, not an identity or chat directory.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Free-text search query (tokenized, scored by term overlap). Mutually exclusive with framed fields.",
"example": "Alice Engineering"
},
"name": {
"type": "string",
"description": "Full or partial person name — firstName + lastName substring match (framed search)",
"example": "Alice Smith"
},
"email": {
"type": "string",
"description": "Email address partial match (framed search)",
"example": "alice.smith"
},
"department": {
"type": "string",
"description": "Exact department name match (framed search)",
"example": "Engineering"
},
"role": {
"type": "string",
"description": "Role/title substring match (framed search)",
"example": "Engineer"
},
"manager_name": {
"type": "string",
"description": "Manager full name substring — applied as post-filter after framed scoring (framed search)",
"example": "Bob Johnson"
},
"manager_email": {
"type": "string",
"description": "Manager email substring — applied as post-filter after framed scoring (framed search)",
"example": "bob.johnson"
},
"employment_status": {
"type": "string",
"enum": [
"active",
"terminated",
"on_leave",
"suspended"
],
"description": "Employment status exact match (framed search)",
"example": "active"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Search results as flat User records (top 5 matches by score)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"record_id": {
"type": "string",
"description": "Employee id",
"example": "EMP-001"
},
"email_addr": {
"type": "string",
"example": "alice.smith@example.com"
},
"full_name": {
"type": "string",
"example": "Alice Smith"
},
"first_name": {
"type": "string",
"example": "Alice"
},
"last_name": {
"type": "string",
"example": "Smith"
},
"department": {
"type": "string",
"nullable": true,
"example": "Engineering"
},
"role": {
"type": "string",
"nullable": true,
"example": "Software Engineer"
},
"manager_email": {
"type": "string",
"nullable": true,
"example": "bob.johnson@example.com"
},
"timezone": {
"type": "string",
"nullable": true,
"example": "America/Los_Angeles"
},
"location": {
"type": "string",
"nullable": true,
"example": "Mountain View"
}
}
}
},
"total_retrieved": {
"type": "integer"
}
}
}
}
}
},
"500": {
"description": "Internal server error"
}
}
}
GET /employees
List HRIS employee records
An HRIS employee is the system-of-record employment file for a person: employee id, name/email, manager, department, title, hire date, salary and compensation, employment status (active / on_leave / terminated / suspended), PTO balance, skills, reviews and goals. This is the HR master data - richer and more sensitive than the identity directory (a people/account lookup) or a chat user roster. Use it for org, headcount, compensation and status questions; use the employee search for fuzzy name matching. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List HRIS employee records",
"description": "An HRIS employee is the system-of-record employment file for a person: employee id, name/email, manager, department, title, hire date, salary and compensation, employment status (active / on_leave / terminated / suspended), PTO balance, skills, reviews and goals. This is the HR master data - richer and more sensitive than the identity directory (a people/account lookup) or a chat user roster. Use it for org, headcount, compensation and status questions; use the employee search for fuzzy name matching. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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."
},
{
"in": "query",
"name": "status",
"required": false,
"schema": {
"type": "string",
"enum": [
"active",
"terminated",
"on_leave",
"suspended"
]
},
"description": "Filter by employment status (exact match). Legacy — prefer $filter.",
"example": "active"
},
{
"in": "query",
"name": "department",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by department name (exact match). Legacy — prefer $filter.",
"example": "Engineering"
},
{
"in": "query",
"name": "firstName",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by first name (exact match). Legacy — prefer $filter.",
"example": "Alice"
},
{
"in": "query",
"name": "lastName",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by last name (exact match). Legacy — prefer $filter.",
"example": "Smith"
},
{
"in": "query",
"name": "email",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by email address (exact match). Legacy — prefer $filter.",
"example": "alice.smith0@acme.com"
},
{
"in": "query",
"name": "managerId",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by manager employee ID (exact match). Legacy — prefer $filter.",
"example": "EMP-0003"
},
{
"in": "query",
"name": "search",
"required": false,
"schema": {
"type": "string"
},
"description": "Free-text search across firstName, lastName, and email (substring match). Legacy — equivalent to $search.",
"example": "alice"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Employee"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /employees
Create an HRIS employee record
Onboards a person into the HR system of record with their employment details. Email must be unique in the instance (409 otherwise). This creates the employment file only - it does not provision identity, chat or any downstream account.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Create an HRIS employee record",
"description": "Onboards a person into the HR system of record with their employment details. Email must be unique in the instance (409 otherwise). This creates the employment file only - it does not provision identity, chat or any downstream account.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Employee"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Employee"
}
}
}
},
"400": {
"description": "Missing required field or invalid status"
},
"409": {
"description": "Employee with this email already exists"
}
}
}
GET /employees/{id}
Get an HRIS employee record by id
Returns one employment file in full - manager, department, title, compensation, status history, PTO balance, skills, reviews and goals. Use the employee search when you have a name rather than an id.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get an HRIS employee record by id",
"description": "Returns one employment file in full - manager, department, title, compensation, status history, PTO balance, skills, reviews and goals. Use the employee search when you have a name rather than an 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/Employee"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /employees/{id}
Update an HRIS employee record
Partial update of the employment file - also the way to write the `skills`, `reviews` and `goals` arrays that the dedicated GET sub-resources read back. Status accepts 'suspended' on top of the standard statuses, but terminated employees cannot be reactivated and their employment fields (salary, department_id, title) are frozen; for lifecycle moves prefer the transition action, and for PTO use the pto-balance action.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Update an HRIS employee record",
"description": "Partial update of the employment file - also the way to write the `skills`, `reviews` and `goals` arrays that the dedicated GET sub-resources read back. Status accepts 'suspended' on top of the standard statuses, but terminated employees cannot be reactivated and their employment fields (salary, department_id, title) are frozen; for lifecycle moves prefer the transition action, and for PTO use the pto-balance action.",
"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/Employee"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Employee"
}
}
}
},
"404": {
"description": "Not found"
},
"422": {
"description": "State machine violation (e.g. reactivating a terminated employee)"
}
}
}
DELETE /employees/{id}
Delete an HRIS employee record
DESTRUCTIVE and irreversible: erases the entire employment file including status history, reviews and PTO. This is NOT how you offboard someone - use the transition action to set status to `terminated`, which preserves the record. Confirm with the user before calling.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete an HRIS employee record",
"description": "DESTRUCTIVE and irreversible: erases the entire employment file including status history, reviews and PTO. This is NOT how you offboard someone - use the transition action to set status to `terminated`, which preserves the record. 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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}
POST /employees/{id}/transition
Move an employee through their lifecycle (leave / return / terminate)
The correct way to change employment status: applies the state machine and appends to status_history with a reason and effective date. Terminated employees cannot be reactivated or put on leave, and an on_leave employee must return to active before termination. Optionally deducts `days` of PTO when going on_leave. Terminating is effectively irreversible - confirm with the user first. Use this rather than PATCHing `status` on the employee.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Move an employee through their lifecycle (leave / return / terminate)",
"description": "The correct way to change employment status: applies the state machine and appends to status_history with a reason and effective date. Terminated employees cannot be reactivated or put on leave, and an on_leave employee must return to active before termination. Optionally deducts `days` of PTO when going on_leave. Terminating is effectively irreversible - confirm with the user first. Use this rather than PATCHing `status` on the employee.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"active",
"terminated",
"on_leave"
],
"description": "Target status for the transition",
"example": "on_leave"
},
"reason": {
"type": "string",
"nullable": true,
"description": "Human-readable reason for the transition (appended to status_history)",
"example": "Medical leave"
},
"effective_date": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "When the transition takes effect (defaults to now)",
"example": "2026-06-01T00:00:00.000Z"
},
"days": {
"type": "number",
"description": "PTO vacation days to deduct from pto_balance.vacation_days. Only applied when transitioning to on_leave.",
"example": 5
}
},
"required": [
"status"
]
}
}
}
},
"responses": {
"200": {
"description": "Updated employee record",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Employee"
}
}
}
},
"400": {
"description": "Invalid status value"
},
"404": {
"description": "Employee not found"
},
"422": {
"description": "Transition not allowed by state machine rules"
}
}
}
PATCH /employees/{id}/pto-balance
Set an employee's PTO day balances
Directly SETS (does not add to) vacation_days and/or sick_days on the employee's pto_balance; omitted fields keep their current value. Use this for balance corrections and accrual grants - booking actual time off goes through time-off requests, and going on leave can deduct days via the transition action.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Set an employee's PTO day balances",
"description": "Directly SETS (does not add to) vacation_days and/or sick_days on the employee's pto_balance; omitted fields keep their current value. Use this for balance corrections and accrual grants - booking actual time off goes through time-off requests, and going on leave can deduct days via the transition action.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"vacation_days": {
"type": "number",
"description": "New vacation day balance. Omit to keep current value.",
"example": 15
},
"sick_days": {
"type": "number",
"description": "New sick day balance. Omit to keep current value.",
"example": 5
}
}
}
}
}
},
"responses": {
"200": {
"description": "Updated employee record with new pto_balance",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Employee"
}
}
}
},
"404": {
"description": "Employee not found"
}
}
}
GET /employees/{id}/skills
Get an employee's skills profile
Read-only convenience view of the `skills` array on one employee record (skill, proficiency, last_updated) - useful for staffing and development questions. To change skills, PATCH the `skills` field on the employee; there is no write endpoint here.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get an employee's skills profile",
"description": "Read-only convenience view of the `skills` array on one employee record (skill, proficiency, last_updated) - useful for staffing and development questions. To change skills, PATCH the `skills` field on the employee; there is no write endpoint here.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Employee skills",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"employee_id": {
"type": "string"
},
"skills": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Employee/properties/skills/items"
}
}
}
}
}
}
},
"404": {
"description": "Employee not found"
}
}
}
GET /employees/{id}/reviews
Get an employee's performance review history
Read-only convenience view of the `reviews` array on one employee record (period, rating, strengths, gaps). This is the narrative review history on the employment file - distinct from review-cycles, which track the review PROCESS. To change reviews, PATCH the `reviews` field on the employee.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get an employee's performance review history",
"description": "Read-only convenience view of the `reviews` array on one employee record (period, rating, strengths, gaps). This is the narrative review history on the employment file - distinct from review-cycles, which track the review PROCESS. To change reviews, PATCH the `reviews` field on the employee.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Employee reviews",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"employee_id": {
"type": "string"
},
"reviews": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Employee/properties/reviews/items"
}
}
}
}
}
}
},
"404": {
"description": "Employee not found"
}
}
}
GET /employees/{id}/goals
Get an employee's development goals
Read-only convenience view of the `goals` array on one employee record (description, target_role, target_timeline, status, progress_pct) - career-development plans, not company OKRs. To change goals, PATCH the `goals` field on the employee.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get an employee's development goals",
"description": "Read-only convenience view of the `goals` array on one employee record (description, target_role, target_timeline, status, progress_pct) - career-development plans, not company OKRs. To change goals, PATCH the `goals` field on the employee.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Employee goals",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"employee_id": {
"type": "string"
},
"goals": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Employee/properties/goals/items"
}
}
}
}
}
}
},
"404": {
"description": "Employee not found"
}
}
}
GET /departments
List HRIS departments
A department is an org unit in the HR system of record - name, its manager and its headcount. Employees reference it by department_id / department name. Use it to enumerate the org structure or resolve a department before filtering employees. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List HRIS departments",
"description": "A department is an org unit in the HR system of record - name, its manager and its headcount. Employees reference it by department_id / department name. Use it to enumerate the org structure or resolve a department before filtering employees. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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."
},
{
"in": "query",
"name": "name",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by department name (exact match)",
"example": "Engineering"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Department"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /departments
Create an HRIS department
Adds an org unit that employees can be assigned to. Creating a department does not move any employee into it - PATCH the employees for that.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Create an HRIS department",
"description": "Adds an org unit that employees can be assigned to. Creating a department does not move any employee into it - PATCH the employees for that.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Department"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Department"
}
}
}
}
}
}
GET /departments/{id}
Get an HRIS department by id
Returns one org unit with its manager and headcount. The employees in it are found by filtering the employees list on that department.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get an HRIS department by id",
"description": "Returns one org unit with its manager and headcount. The employees in it are found by filtering the employees list on that department.",
"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/Department"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /departments/{id}
Edit an HRIS department
Partial update of an org unit (rename, change its manager). Renaming does not automatically re-point employees that store the department by name.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Edit an HRIS department",
"description": "Partial update of an org unit (rename, change its manager). Renaming does not automatically re-point employees that store the department by name.",
"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/Department"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Department"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
DELETE /departments/{id}
Delete an HRIS department
DESTRUCTIVE and irreversible: removes the org unit. Employees that referenced it keep a department that no longer resolves. Confirm with the user before calling.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete an HRIS department",
"description": "DESTRUCTIVE and irreversible: removes the org unit. Employees that referenced it keep a department that no longer resolves. 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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /pay-runs
List payroll runs
A pay run is one payroll cycle for the company - its pay period, pay date, employee count, gross/net totals and a status of draft -> processing -> completed (or cancelled). Company-level payroll, not an individual payslip; per-person hours live on timesheets. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List payroll runs",
"description": "A pay run is one payroll cycle for the company - its pay period, pay date, employee count, gross/net totals and a status of draft -> processing -> completed (or cancelled). Company-level payroll, not an individual payslip; per-person hours live on timesheets. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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."
},
{
"in": "query",
"name": "status",
"required": false,
"schema": {
"type": "string",
"enum": [
"draft",
"processing",
"completed",
"cancelled"
]
},
"description": "Filter by pay run status (exact match)",
"example": "draft"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PayRun"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /pay-runs
Open a payroll run
Creates a payroll cycle, normally in `draft`, for a pay period. Advance it with the update operation - it must pass through `processing` before it can be completed.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Open a payroll run",
"description": "Creates a payroll cycle, normally in `draft`, for a pay period. Advance it with the update operation - it must pass through `processing` before it can be completed.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PayRun"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PayRun"
}
}
}
}
}
}
GET /pay-runs/{id}
Get a payroll run by id
Returns one payroll cycle: period, pay date, employee count, gross and net totals and current status.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a payroll run by id",
"description": "Returns one payroll cycle: period, pay date, employee count, gross and net totals and current status.",
"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/PayRun"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /pay-runs/{id}
Advance or edit a payroll run
Partial update, and the way a run moves through its lifecycle. State machine: a completed run is LOCKED and cannot be edited, a cancelled run cannot be reactivated, and draft cannot jump straight to completed (it must pass through processing). Completing a run is effectively irreversible - confirm with the user.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Advance or edit a payroll run",
"description": "Partial update, and the way a run moves through its lifecycle. State machine: a completed run is LOCKED and cannot be edited, a cancelled run cannot be reactivated, and draft cannot jump straight to completed (it must pass through processing). Completing a run is effectively irreversible - confirm with the user.",
"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/PayRun"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PayRun"
}
}
}
},
"404": {
"description": "Not found"
},
"422": {
"description": "State machine violation (e.g. modifying a completed run, or skipping processing status)"
}
}
}
DELETE /pay-runs/{id}
Delete a payroll run
DESTRUCTIVE and irreversible: erases the payroll cycle and its totals. Prefer setting status to `cancelled`, which keeps the audit trail.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a payroll run",
"description": "DESTRUCTIVE and irreversible: erases the payroll cycle and its totals. Prefer setting status to `cancelled`, which keeps the audit trail.",
"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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /timesheets
List employee timesheets
A timesheet is one employee's hours for one week (week start, regular and overtime hours) with a status of submitted / approved / rejected. Per-person time capture that feeds payroll; the payroll cycle itself is a pay run, and paid absence is a time-off request. This is the WEEKLY SUMMARY that feeds payroll, not a punch-level record: it has no clock in/out times, breaks, or meal-break compliance. For punches, punch-derived timecards per pay period, schedules and meal-break exceptions, use Purple Time (/purple-suite/time) — the source of truth for hours actually worked. Prefer this endpoint for payroll-facing weekly hours. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List employee timesheets",
"description": "A timesheet is one employee's hours for one week (week start, regular and overtime hours) with a status of submitted / approved / rejected. Per-person time capture that feeds payroll; the payroll cycle itself is a pay run, and paid absence is a time-off request. This is the WEEKLY SUMMARY that feeds payroll, not a punch-level record: it has no clock in/out times, breaks, or meal-break compliance. For punches, punch-derived timecards per pay period, schedules and meal-break exceptions, use Purple Time (/purple-suite/time) — the source of truth for hours actually worked. Prefer this endpoint for payroll-facing weekly hours. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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."
},
{
"in": "query",
"name": "employee_id",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by employee ID (exact match)",
"example": "EMP-0001"
},
{
"in": "query",
"name": "status",
"required": false,
"schema": {
"type": "string",
"enum": [
"submitted",
"approved",
"rejected"
]
},
"description": "Filter by timesheet status (exact match)",
"example": "submitted"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Timesheet"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /timesheets
Submit a weekly timesheet
Records an employee's hours for one week. One timesheet per employee per week (409 on a duplicate), hours are range-checked, and terminated employees are rejected with 422.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Submit a weekly timesheet",
"description": "Records an employee's hours for one week. One timesheet per employee per week (409 on a duplicate), hours are range-checked, and terminated employees are rejected with 422.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Timesheet"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Timesheet"
}
}
}
},
"400": {
"description": "Missing required field or out-of-range hours"
},
"404": {
"description": "Employee not found"
},
"409": {
"description": "Timesheet already exists for this employee and week"
},
"422": {
"description": "Cannot submit timesheet for a terminated employee"
}
}
}
GET /timesheets/{id}
Get a timesheet by id
Returns one employee-week of hours with its regular/overtime split and approval status.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a timesheet by id",
"description": "Returns one employee-week of hours with its regular/overtime split and approval status.",
"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/Timesheet"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /timesheets/{id}
Approve, reject or correct a timesheet
Partial update - the way a manager sets `status` to approved or rejected, and the way logged hours are corrected before payroll picks them up.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Approve, reject or correct a timesheet",
"description": "Partial update - the way a manager sets `status` to approved or rejected, and the way logged hours are corrected before payroll picks them up.",
"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/Timesheet"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Timesheet"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
DELETE /timesheets/{id}
Delete a timesheet
DESTRUCTIVE and irreversible: removes the week's recorded hours. Prefer rejecting the timesheet, which keeps the record.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a timesheet",
"description": "DESTRUCTIVE and irreversible: removes the week's recorded hours. Prefer rejecting the timesheet, which keeps the record.",
"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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /benefits
List employee benefit enrollments
A benefit record is one employee's ENROLLMENT in a plan - the benefit type (insurance / retirement / wellness / health), plan name, coverage level, employee and employer contributions and a status of active / pending / terminated. These are per-person enrollments, not a catalog of available plans. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List employee benefit enrollments",
"description": "A benefit record is one employee's ENROLLMENT in a plan - the benefit type (insurance / retirement / wellness / health), plan name, coverage level, employee and employer contributions and a status of active / pending / terminated. These are per-person enrollments, not a catalog of available plans. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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."
},
{
"in": "query",
"name": "employee_id",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by employee ID (exact match)",
"example": "EMP-0001"
},
{
"in": "query",
"name": "type",
"required": false,
"schema": {
"type": "string",
"enum": [
"insurance",
"retirement",
"wellness",
"health"
]
},
"description": "Filter by benefit type (exact match)",
"example": "insurance"
},
{
"in": "query",
"name": "status",
"required": false,
"schema": {
"type": "string",
"enum": [
"active",
"pending",
"terminated"
]
},
"description": "Filter by benefit status (exact match)",
"example": "active"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Benefit"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /benefits
Enroll an employee in a benefit plan
Creates an enrollment for one employee. An employee may hold only one ACTIVE benefit of a given type (422 otherwise), so terminate the existing enrollment before switching plans.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Enroll an employee in a benefit plan",
"description": "Creates an enrollment for one employee. An employee may hold only one ACTIVE benefit of a given type (422 otherwise), so terminate the existing enrollment before switching plans.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Benefit"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Benefit"
}
}
}
},
"400": {
"description": "Missing required field or invalid type"
},
"404": {
"description": "Employee not found"
},
"422": {
"description": "Employee already has an active benefit of this type"
}
}
}
GET /benefits/{id}
Get a benefit enrollment by id
Returns one employee's enrollment: plan, coverage level, contributions, effective dates and status.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a benefit enrollment by id",
"description": "Returns one employee's enrollment: plan, coverage level, contributions, effective dates and status.",
"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/Benefit"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /benefits/{id}
Change or terminate a benefit enrollment
Partial update of one enrollment - change coverage or contributions, or set `status` to terminated to end it while keeping the history.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Change or terminate a benefit enrollment",
"description": "Partial update of one enrollment - change coverage or contributions, or set `status` to terminated to end it while keeping the history.",
"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/Benefit"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Benefit"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
DELETE /benefits/{id}
Delete a benefit enrollment
DESTRUCTIVE and irreversible: erases the enrollment and its history. Prefer setting `status` to terminated.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a benefit enrollment",
"description": "DESTRUCTIVE and irreversible: erases the enrollment and its history. Prefer setting `status` to terminated.",
"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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /review-cycles
List performance review cycles
A review cycle tracks the performance-review PROCESS for an employee in a period: reviewer, due date, overall rating and a status of planning / in_progress / completed / cancelled. Use it for 'is my review done / who is my reviewer'; the narrative outcomes live in the employee's reviews sub-resource. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List performance review cycles",
"description": "A review cycle tracks the performance-review PROCESS for an employee in a period: reviewer, due date, overall rating and a status of planning / in_progress / completed / cancelled. Use it for 'is my review done / who is my reviewer'; the narrative outcomes live in the employee's reviews sub-resource. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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."
},
{
"in": "query",
"name": "employee_id",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by employee ID (exact match)",
"example": "EMP-0001"
},
{
"in": "query",
"name": "status",
"required": false,
"schema": {
"type": "string",
"enum": [
"planning",
"in_progress",
"completed",
"cancelled"
]
},
"description": "Filter by review cycle status (exact match)",
"example": "in_progress"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ReviewCycle"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /review-cycles
Open a performance review cycle
Starts a review for an employee in a period, with a reviewer and due date. It schedules the process; it does not itself record a rating narrative.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Open a performance review cycle",
"description": "Starts a review for an employee in a period, with a reviewer and due date. It schedules the process; it does not itself record a rating narrative.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReviewCycle"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReviewCycle"
}
}
}
}
}
}
GET /review-cycles/{id}
Get a performance review cycle by id
Returns one review's period, reviewer, due date, status and overall rating if recorded.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a performance review cycle by id",
"description": "Returns one review's period, reviewer, due date, status and overall rating if recorded.",
"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/ReviewCycle"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /review-cycles/{id}
Progress or complete a review cycle
Partial update - the way a review moves from planning to in_progress to completed and an overall rating is recorded.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Progress or complete a review cycle",
"description": "Partial update - the way a review moves from planning to in_progress to completed and an overall rating is recorded.",
"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/ReviewCycle"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReviewCycle"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
DELETE /review-cycles/{id}
Delete a performance review cycle
DESTRUCTIVE and irreversible: removes the review record and its rating. Prefer setting status to `cancelled`.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a performance review cycle",
"description": "DESTRUCTIVE and irreversible: removes the review record and its rating. Prefer setting status to `cancelled`.",
"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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /time-off-requests
List time-off (PTO) requests
A time-off request is one employee's absence request - type (vacation / sick / personal / bereavement / jury_duty), date range, day count and a status of pending / approved / denied / cancelled. Use it for 'who is out' and approval queues; the remaining day balance lives on the employee's pto_balance. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List time-off (PTO) requests",
"description": "A time-off request is one employee's absence request - type (vacation / sick / personal / bereavement / jury_duty), date range, day count and a status of pending / approved / denied / cancelled. Use it for 'who is out' and approval queues; the remaining day balance lives on the employee's pto_balance. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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."
},
{
"in": "query",
"name": "employee_id",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter by employee ID (exact match)",
"example": "EMP-0001"
},
{
"in": "query",
"name": "status",
"required": false,
"schema": {
"type": "string",
"enum": [
"pending",
"approved",
"denied",
"cancelled"
]
},
"description": "Filter by request status (exact match)",
"example": "pending"
},
{
"in": "query",
"name": "type",
"required": false,
"schema": {
"type": "string",
"enum": [
"vacation",
"sick",
"personal",
"bereavement",
"jury_duty"
]
},
"description": "Filter by time-off type (exact match)",
"example": "vacation"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TimeOffRequest"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /time-off-requests
Submit a time-off (PTO) request
The employee-facing way to ASK for time off: creates the request in `pending` for an approver to decide via the update operation. It does not deduct the PTO balance - use the pto-balance action for that. Fires the `hris.time_off_request.created` outbound webhook to any configured subscriber.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Submit a time-off (PTO) request",
"description": "The employee-facing way to ASK for time off: creates the request in `pending` for an approver to decide via the update operation. It does not deduct the PTO balance - use the pto-balance action for that. Fires the `hris.time_off_request.created` outbound webhook to any configured subscriber.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TimeOffRequest"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TimeOffRequest"
}
}
}
},
"400": {
"description": "Missing required field or invalid type/status"
},
"404": {
"description": "Employee not found"
}
}
}
GET /time-off-requests/{id}
Get a time-off (PTO) request by id
Returns one absence request: employee, type, date range, day count, status and reason.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a time-off (PTO) request by id",
"description": "Returns one absence request: employee, type, date range, day count, status and reason.",
"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/TimeOffRequest"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /time-off-requests/{id}
Approve, deny or amend a time-off request
The approver-facing counterpart of submitting: partial update, typically setting `status` to approved, denied or cancelled, and also used to amend dates. Fires the `hris.time_off_request.updated` outbound webhook. Approving does not itself adjust the employee's pto_balance.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Approve, deny or amend a time-off request",
"description": "The approver-facing counterpart of submitting: partial update, typically setting `status` to approved, denied or cancelled, and also used to amend dates. Fires the `hris.time_off_request.updated` outbound webhook. Approving does not itself adjust the employee's pto_balance.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "Fields to update (partial), e.g. { \"status\": \"approved\" }",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TimeOffRequest"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TimeOffRequest"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /work-authorizations
List work authorizations (visas / right to work)
A work authorization is one person's immigration status in a country - type (citizen, permanent_resident, h1b, skilled_worker, eu_blue_card, ...), country, expiry date, renewal state and any travel restrictions. Use it for right-to-work, visa-expiry and travel-eligibility questions (it pairs with an expense trip's destination_country). Convenience: pass employee=me (the impersonated/default user) or employee=<email> to scope to one person. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List work authorizations (visas / right to work)",
"description": "A work authorization is one person's immigration status in a country - type (citizen, permanent_resident, h1b, skilled_worker, eu_blue_card, ...), country, expiry date, renewal state and any travel restrictions. Use it for right-to-work, visa-expiry and travel-eligibility questions (it pairs with an expense trip's destination_country). Convenience: pass employee=me (the impersonated/default user) or employee=<email> to scope to one person. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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."
},
{
"in": "query",
"name": "employee",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter to one employee — an email, or the literal 'me' (resolves to the impersonated/default user).",
"example": "me"
},
{
"in": "query",
"name": "status",
"required": false,
"schema": {
"type": "string",
"enum": [
"valid",
"expiring_soon",
"expired",
"renewal_pending",
"renewal_filed"
]
},
"description": "Filter by status (exact match)",
"example": "valid"
},
{
"in": "query",
"name": "country",
"required": false,
"schema": {
"type": "string",
"enum": [
"IN",
"AU",
"US",
"GB",
"KR",
"DE"
]
},
"description": "Filter by country (exact match)",
"example": "US"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/WorkAuthorization"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /work-authorizations
Record a work authorization for an employee
Registers a person's right-to-work status in a country (type, expiry, sponsorship). Types citizen and permanent_resident have no expiry and cannot be renewed.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Record a work authorization for an employee",
"description": "Registers a person's right-to-work status in a country (type, expiry, sponsorship). Types citizen and permanent_resident have no expiry and cannot be renewed.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WorkAuthorization"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WorkAuthorization"
}
}
}
},
"400": {
"description": "Validation error"
}
}
}
GET /work-authorizations/{id}
Get a work authorization by id
Returns one immigration record: type, country, expiry date, status, renewal state and travel restrictions.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a work authorization by id",
"description": "Returns one immigration record: type, country, expiry date, status, renewal state and travel restrictions.",
"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/WorkAuthorization"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /work-authorizations/{id}
Edit a work authorization
Partial update of an immigration record (correct the expiry, type or restrictions). To file an extension use the renew action, which applies the state machine and extends the expiry for you.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Edit a work authorization",
"description": "Partial update of an immigration record (correct the expiry, type or restrictions). To file an extension use the renew action, which applies the state machine and extends the expiry for you.",
"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/WorkAuthorization"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WorkAuthorization"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
DELETE /work-authorizations/{id}
Delete a work authorization
DESTRUCTIVE and irreversible: removes the person's right-to-work record, which can make them appear unauthorized. Confirm with the user before calling.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a work authorization",
"description": "DESTRUCTIVE and irreversible: removes the person's right-to-work record, which can make them appear unauthorized. 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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}
POST /work-authorizations/{id}/renew
File a renewal / extension for a work authorization
The action to take when a visa is expiring: transitions an expiring_soon / expired / renewal_pending authorization to renewal_filed, pushes expiry_date out by `extend_months` (default 24) and clears travel restrictions that were tied to the pending renewal. Rejected with 422 for citizen / permanent_resident (no expiry) or when a renewal has already been filed. Use this rather than PATCHing the expiry by hand.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "File a renewal / extension for a work authorization",
"description": "The action to take when a visa is expiring: transitions an expiring_soon / expired / renewal_pending authorization to renewal_filed, pushes expiry_date out by `extend_months` (default 24) and clears travel restrictions that were tied to the pending renewal. Rejected with 422 for citizen / permanent_resident (no expiry) or when a renewal has already been filed. Use this rather than PATCHing the expiry by hand.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"extend_months": {
"type": "integer",
"default": 24,
"description": "Months to extend expiry_date by (default 24)."
},
"notes": {
"type": "string",
"example": "Extension petition filed."
}
}
}
}
}
},
"responses": {
"200": {
"description": "Renewal filed",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WorkAuthorization"
}
}
}
},
"404": {
"description": "Authorization not found"
},
"422": {
"description": "Authorization cannot be renewed (no expiry, or already filed)"
}
}
}
GET /job-families
List job families (career groupings)
A job family is the top of the HRIS job architecture - a career grouping such as Engineering or Finance that job profiles and job roles hang off. Reference catalog, not people: use employees for who holds a job. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List job families (career groupings)",
"description": "A job family is the top of the HRIS job architecture - a career grouping such as Engineering or Finance that job profiles and job roles hang off. Reference catalog, not people: use employees for who holds a job. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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/JobFamily"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /job-families
Create a job family
Adds a career grouping to the job architecture. The id is server-derived from the name; duplicates are rejected with 409.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Create a job family",
"description": "Adds a career grouping to the job architecture. The id is server-derived from the name; duplicates are rejected with 409.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobFamily"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobFamily"
}
}
}
},
"400": {
"description": "Validation / missing reference"
},
"409": {
"description": "Duplicate"
}
}
}
GET /job-families/{id}
Get a job family by id
Returns one career grouping's name and description.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a job family by id",
"description": "Returns one career grouping's name and description.",
"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/JobFamily"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /job-families/{id}
Rename or edit a job family
Partial update of a career grouping. Renaming CASCADES to the job profiles and job roles that reference it by name.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Rename or edit a job family",
"description": "Partial update of a career grouping. Renaming CASCADES to the job profiles and job roles that reference it by name.",
"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/JobFamily"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobFamily"
}
}
}
},
"400": {
"description": "Validation / missing reference"
},
"404": {
"description": "Not found"
}
}
}
DELETE /job-families/{id}
Delete a job family
DESTRUCTIVE: removes the career grouping. Blocked with 409 while job profiles or job roles still reference it, so clear dependents first.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a job family",
"description": "DESTRUCTIVE: removes the career grouping. Blocked with 409 while job profiles or job roles still reference it, so clear dependents first.",
"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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
},
"409": {
"description": "Blocked — still referenced by dependents"
}
}
}
GET /job-levels
List job levels (leveling framework grades)
A job level is one grade in the leveling framework - a code such as IC3 or M2 with its track (IC or M), numeric tier and management level. Use it to compare seniority or to resolve the job_level on a job profile. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List job levels (leveling framework grades)",
"description": "A job level is one grade in the leveling framework - a code such as IC3 or M2 with its track (IC or M), numeric tier and management level. Use it to compare seniority or to resolve the job_level on a job profile. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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/JobLevel"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /job-levels
Create a job level
Adds a grade to the leveling framework (code, track, tier). Reference-catalog administration; it does not re-level any employee.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Create a job level",
"description": "Adds a grade to the leveling framework (code, track, tier). Reference-catalog administration; it does not re-level any employee.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobLevel"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobLevel"
}
}
}
},
"400": {
"description": "Validation / missing reference"
},
"409": {
"description": "Duplicate"
}
}
}
GET /job-levels/{id}
Get a job level by id
Returns one grade: its code, display name, track, tier and management level.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a job level by id",
"description": "Returns one grade: its code, display name, track, tier and management level.",
"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/JobLevel"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /job-levels/{id}
Rename or edit a job level
Partial update of a grade's name, track or tier. Renaming CASCADES to job profiles that reference the level code.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Rename or edit a job level",
"description": "Partial update of a grade's name, track or tier. Renaming CASCADES to job profiles that reference the level code.",
"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/JobLevel"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobLevel"
}
}
}
},
"400": {
"description": "Validation / missing reference"
},
"404": {
"description": "Not found"
}
}
}
DELETE /job-levels/{id}
Delete a job level
DESTRUCTIVE: removes the grade from the framework. Blocked with 409 while job profiles still reference it.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a job level",
"description": "DESTRUCTIVE: removes the grade from the framework. Blocked with 409 while job profiles still reference 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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
},
"409": {
"description": "Blocked — still referenced by dependents"
}
}
}
GET /job-profiles
List job profiles (role templates: title + family + level)
A job profile is the template that ties a TITLE to a job family and a job level (e.g. Software Engineer / Engineering / IC3). It is the canonical answer to 'what titles exist and where do they sit' - employees carry the title, this defines it. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List job profiles (role templates: title + family + level)",
"description": "A job profile is the template that ties a TITLE to a job family and a job level (e.g. Software Engineer / Engineering / IC3). It is the canonical answer to 'what titles exist and where do they sit' - employees carry the title, this defines it. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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/JobProfile"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /job-profiles
Create a job profile
Defines a new titled role template against an existing job family and job level. Missing references are rejected with 400.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Create a job profile",
"description": "Defines a new titled role template against an existing job family and job level. Missing references are rejected with 400.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobProfile"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobProfile"
}
}
}
},
"400": {
"description": "Validation / missing reference"
},
"409": {
"description": "Duplicate"
}
}
}
GET /job-profiles/{id}
Get a job profile by id
Returns one role template: its title, job family, job level and management level.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a job profile by id",
"description": "Returns one role template: its title, job family, job level and management level.",
"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/JobProfile"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /job-profiles/{id}
Edit a job profile
Partial update of a role template's title, family or level. Renaming CASCADES to job roles that reference this profile.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Edit a job profile",
"description": "Partial update of a role template's title, family or level. Renaming CASCADES to job roles that reference this profile.",
"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/JobProfile"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobProfile"
}
}
}
},
"400": {
"description": "Validation / missing reference"
},
"404": {
"description": "Not found"
}
}
}
DELETE /job-profiles/{id}
Delete a job profile
DESTRUCTIVE: removes the role template. Blocked with 409 while job roles still reference it.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a job profile",
"description": "DESTRUCTIVE: removes the role template. Blocked with 409 while job roles still reference 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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
},
"409": {
"description": "Blocked — still referenced by dependents"
}
}
}
GET /job-roles
List job roles (positions under a family)
A job role is a named position under a job family, optionally mapped to a job profile (modeled on ServiceNow sn_hr_core_position). It is the most concrete layer of the job architecture: family -> profile -> role. 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).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List job roles (positions under a family)",
"description": "A job role is a named position under a job family, optionally mapped to a job profile (modeled on ServiceNow sn_hr_core_position). It is the most concrete layer of the job architecture: family -> profile -> role. 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": "Max items per page (default 50)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pass the previous response's nextCursor to fetch the next page"
},
{
"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/JobRole"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page"
},
"total": {
"type": "integer",
"description": "Total documents in the collection"
}
}
}
}
}
}
}
}
POST /job-roles
Create a job role
Adds a position under an existing job family and, optionally, a job profile. Missing references are rejected with 400.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Create a job role",
"description": "Adds a position under an existing job family and, optionally, a job profile. Missing references are rejected with 400.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobRole"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobRole"
}
}
}
},
"400": {
"description": "Validation / missing reference"
},
"409": {
"description": "Duplicate"
}
}
}
GET /job-roles/{id}
Get a job role by id
Returns one position: its name, description, active flag, job family and mapped job profile.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a job role by id",
"description": "Returns one position: its name, description, active flag, job family and mapped job profile.",
"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/JobRole"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /job-roles/{id}
Edit a job role
Partial update of a position's name, description, family/profile mapping or active flag.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Edit a job role",
"description": "Partial update of a position's name, description, family/profile mapping 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/JobRole"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/JobRole"
}
}
}
},
"400": {
"description": "Validation / missing reference"
},
"404": {
"description": "Not found"
}
}
}
DELETE /job-roles/{id}
Delete a job role
DESTRUCTIVE: removes the position from the catalog. Blocked with 409 while dependents still reference it; prefer setting `active` to false to retire a role.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a job role",
"description": "DESTRUCTIVE: removes the position from the catalog. Blocked with 409 while dependents still reference it; prefer setting `active` to false to retire a role.",
"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"
},
"id": {
"type": "string"
}
}
}
}
}
},
"404": {
"description": "Not found"
},
"409": {
"description": "Blocked — still referenced by dependents"
}
}
}