Identity governance and access requests. Resembles tools like SailPoint, Saviynt and Okta Identity Governance: a catalog of internal systems, each with its own roles and its own environments; access requests that name a system, role and environment; security attestations filed against a request; and provisioning grants. Production environments require an attestation before provisioning, but no endpoint enforces that — each endpoint validates its own payload only, and GET /access-status/{requestId} reports what is on record.
---
**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`.
Identity governance and access requests. Resembles tools like SailPoint, Saviynt and Okta Identity Governance: a catalog of internal systems, each with its own roles and its own environments; access requests that name a system, role and environment; security attestations filed against a request; and provisioning grants. Production environments require an attestation before provisioning, but no endpoint enforces that — each endpoint validates its own payload only, and GET /access-status/{requestId} reports what is on record.
---
**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
GET /systems
List systems access can be requested for
The catalog of internal systems. Each system has its own roles (GET /systems/{systemId}/roles) and its own environments (GET /systems/{systemId}/environments); ids from those lists are what an access request expects.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List systems access can be requested for",
"description": "The catalog of internal systems. Each system has its own roles (GET /systems/{systemId}/roles) and its own environments (GET /systems/{systemId}/environments); ids from those lists are what an access request expects.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AccessSystem"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
}
}
}
GET /systems/{id}
Get a system
Reads one system by systemId.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a system",
"description": "Reads one system by systemId.",
"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/AccessSystem"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /systems/{systemId}/roles
List the roles available on a system
Roles differ per system and role ids do not overlap between systems. Use the id of one of these as roleId on an access request for this system. 404 for an unknown systemId.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "access__list__system_roles",
"summary": "List the roles available on a system",
"description": "Roles differ per system and role ids do not overlap between systems. Use the id of one of these as roleId on an access request for this system. 404 for an unknown systemId.",
"parameters": [
{
"in": "path",
"name": "systemId",
"required": true,
"schema": {
"type": "string"
},
"description": "id of a system from GET /systems."
},
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SystemRole"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
},
"404": {
"description": "Unknown systemId"
}
}
}
GET /systems/{systemId}/environments
List the environments available on a system
Environments differ per system. Each says whether it requiresAttestation: when true, a security attestation must be filed on the request (POST /requests/{id}/attestations) before it is provisioned. 404 for an unknown systemId.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "access__list__system_environments",
"summary": "List the environments available on a system",
"description": "Environments differ per system. Each says whether it requiresAttestation: when true, a security attestation must be filed on the request (POST /requests/{id}/attestations) before it is provisioned. 404 for an unknown systemId.",
"parameters": [
{
"in": "path",
"name": "systemId",
"required": true,
"schema": {
"type": "string"
},
"description": "id of a system from GET /systems."
},
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SystemEnvironment"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
},
"404": {
"description": "Unknown systemId"
}
}
}
GET /requests
List access requests
Access requests in Purple Access. Filter with requesterEmail, systemId or status, or with _filter (e.g. status eq 'open').
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List access requests",
"description": "Access requests in Purple Access. Filter with requesterEmail, systemId or status, or with _filter (e.g. status eq 'open').",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
},
{
"in": "query",
"name": "requesterEmail",
"schema": {
"type": "string"
},
"description": "Exact-match filter on requesterEmail."
},
{
"in": "query",
"name": "systemId",
"schema": {
"type": "string"
},
"description": "Exact-match filter on systemId."
},
{
"in": "query",
"name": "status",
"schema": {
"type": "string",
"enum": [
"open",
"provisioned"
]
},
"description": "Exact-match filter on status."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AccessRequest"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
}
}
}
POST /requests
Create an access request
Opens an access request. Requires requesterEmail, systemId (must exist), roleId, environmentId and justification. roleId and environmentId are recorded as given: this endpoint does not check that they belong to the system, so take them from the system's own role and environment lists. Returns the request with status "open"; its id is what attestations, provisioning and access-status expect.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Create an access request",
"description": "Opens an access request. Requires requesterEmail, systemId (must exist), roleId, environmentId and justification. roleId and environmentId are recorded as given: this endpoint does not check that they belong to the system, so take them from the system's own role and environment lists. Returns the request with status \"open\"; its id is what attestations, provisioning and access-status expect.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccessRequest"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccessRequest"
}
}
}
}
}
}
GET /requests/{id}
Get an access request
Reads one access request by id.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get an access request",
"description": "Reads one access request by 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/AccessRequest"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /requests/{id}
Update an access request
Changes systemId, roleId, environmentId, justification or requesterEmail on an existing request (recorded as given; systemId must exist). Attestations already on the request keep the system + environment they were filed for, so a change of system or environment leaves them stale. Bumps updatedAt.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Update an access request",
"description": "Changes systemId, roleId, environmentId, justification or requesterEmail on an existing request (recorded as given; systemId must exist). Attestations already on the request keep the system + environment they were filed for, so a change of system or environment leaves them stale. Bumps updatedAt.",
"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/AccessRequest"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccessRequest"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
POST /requests/{id}/attestations
File a security attestation on an access request
Puts a security attestation on the request. The attestation snapshots the request's current systemId and environmentId; it covers only that combination. Requires statement and attestedBy. 404 for an unknown request id.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "access__create__request_attestations",
"summary": "File a security attestation on an access request",
"description": "Puts a security attestation on the request. The attestation snapshots the request's current systemId and environmentId; it covers only that combination. Requires statement and attestedBy. 404 for an unknown request id.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
},
"description": "id of an existing access request."
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AttestationInput"
}
}
}
},
"responses": {
"201": {
"description": "Filed",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Attestation"
}
}
}
},
"400": {
"description": "Missing statement or attestedBy"
},
"404": {
"description": "Unknown request id"
}
}
}
POST /requests/{id}/provision
Provision the access on a request
Grants the access the request currently describes. The grant snapshots systemId, roleId and environmentId at grant time and sets the request's status to "provisioned". This endpoint validates only its own payload: it does not check for an attestation or whether the role and environment belong to the system. Optional note. 404 for an unknown request id.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "access__create__request_provision",
"summary": "Provision the access on a request",
"description": "Grants the access the request currently describes. The grant snapshots systemId, roleId and environmentId at grant time and sets the request's status to \"provisioned\". This endpoint validates only its own payload: it does not check for an attestation or whether the role and environment belong to the system. Optional note. 404 for an unknown request id.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
},
"description": "id of an existing access request."
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProvisionInput"
}
}
}
},
"responses": {
"201": {
"description": "Provisioned",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProvisioningGrant"
}
}
}
},
"404": {
"description": "Unknown request id"
}
}
}
GET /attestations
List security attestations
Attestations filed on access requests, each with the systemId + environmentId it was filed for. Filter with requestId to see what covers one request.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List security attestations",
"description": "Attestations filed on access requests, each with the systemId + environmentId it was filed for. Filter with requestId to see what covers one request.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Attestation"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
}
}
}
GET /attestations/{id}
Get an attestation
Reads one attestation by id.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get an attestation",
"description": "Reads one attestation by 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/Attestation"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /grants
List provisioning grants
Grants issued by POST /requests/{id}/provision, each with the systemId, roleId and environmentId it was issued for. Filter with requestId.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List provisioning grants",
"description": "Grants issued by POST /requests/{id}/provision, each with the systemId, roleId and environmentId it was issued for. Filter with requestId.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProvisioningGrant"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
}
}
}
GET /grants/{id}
Get a provisioning grant
Reads one grant by id.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a provisioning grant",
"description": "Reads one grant by 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/ProvisioningGrant"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /access-status/{requestId}
Get what is on record for an access request
What is actually on record for one access request: the current record, every attestation filed on it (each with the systemId + environmentId it was filed for), every provisioning grant issued on it (each with the systemId, roleId and environmentId it was issued for), and which steps have records. Facts only; nothing here is evaluated. Use this to verify what is on record before telling the requester anything.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "access__get__access_status",
"summary": "Get what is on record for an access request",
"description": "What is actually on record for one access request: the current record, every attestation filed on it (each with the systemId + environmentId it was filed for), every provisioning grant issued on it (each with the systemId, roleId and environmentId it was issued for), and which steps have records. Facts only; nothing here is evaluated. Use this to verify what is on record before telling the requester anything.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "requestId",
"required": true,
"schema": {
"type": "string"
},
"description": "id of the access request."
}
],
"responses": {
"200": {
"description": "Access status",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccessStatus"
}
}
}
},
"404": {
"description": "Unknown request id"
}
}
}
GET /requests/{requestId}/verdict
Evaluate the access policy for a request (evaluators only)
Ground truth for graders, not a tool for agents ("x-mcp": false, so the unified MCP server does not derive it). Evaluates the request's current record and every grant's own snapshot against the system catalog and the attestations on file: checks (role and environment validity, whether an attestation is required and whether one matches the current record) plus a single verdict.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "access__get__request_verdict",
"x-mcp": false,
"summary": "Evaluate the access policy for a request (evaluators only)",
"description": "Ground truth for graders, not a tool for agents (\"x-mcp\": false, so the unified MCP server does not derive it). Evaluates the request's current record and every grant's own snapshot against the system catalog and the attestations on file: checks (role and environment validity, whether an attestation is required and whether one matches the current record) plus a single verdict.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "requestId",
"required": true,
"schema": {
"type": "string"
},
"description": "id of the access request."
}
],
"responses": {
"200": {
"description": "Verdict",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccessVerdict"
}
}
}
},
"404": {
"description": "Unknown request id"
}
}
}