Skip to content

PAM API

This reference covers the Privileged Access Management (PAM) REST surface: the admin control plane under /api/v1/pam, the device actuation endpoint, and the agent-side ingest endpoint. For the administrative workflow these endpoints back, see Privileged Access Management; for the trust model, see the PAM Security Model.

All paths are relative to the API base URL and version prefix (/api/v1). See the API Overview for authentication, base URL, and common conventions.

The /pam/* control-plane endpoints require a JWT bearer token (or API key) and run under organization, partner, or system scope. Authorization maps to the device permission set:

Operation class Permission Extra gate
Read (list, active, rules, config, signer groups) devices:read
Rule create / update / delete, signer groups, org config pam:manage_policy MFA (rule preview needs the permission but not MFA)
Approve / deny / revoke (and the assertion challenge) pam:approve MFA on respond and revoke

Tenant isolation is enforced by row-level security on every query. Site-restricted technicians are additionally narrowed to their allowed sites: requests outside those sites are filtered from reads and rejected on writes with 403.


Method Path Description
GET /pam/elevation-requests List and filter elevation requests (Requests and Audit tabs).
GET /pam/active List currently-active (unexpired, approved) elevations.
POST /pam/elevation-requests/:id/assertion-challenge Issue a short-lived WebAuthn assertion challenge for a step-up (Windows Hello / Touch ID) approval.
POST /pam/elevation-requests/:id/respond Approve or deny a pending request.
POST /pam/elevation-requests/:id/revoke Revoke an active elevation before it expires.

GET /pam/elevation-requests

Query parameters (all optional):

Param Type Notes
status enum pending, approved, auto_approved, denied, expired, revoked, actuating
flowType enum uac_intercept, tech_jit_admin, ai_tool_action
deviceId uuid Filter to one device
siteId uuid Filter to one site (must be within the caller’s site scope)
from / to ISO-8601 Bound by request time
page / limit int limit max 100, default 50

Response:

{
"success": true,
"requests": [
{
"id": "",
"orgId": "",
"deviceId": "",
"deviceHostname": "WIN-ACCT-04",
"siteName": "HQ",
"flowType": "uac_intercept",
"status": "pending",
"subjectUsername": "acme\\sarah",
"targetExecutablePath": "C:\\Users\\sarah\\Downloads\\FirefoxSetup.exe",
"targetExecutableSigner": "Mozilla Corporation",
"requestedAt": "2026-06-13T14:02:11Z",
"approvedByName": null,
"deniedByName": null,
"revokedByName": null,
"matchedPolicyName": null,
"pamRuleId": null,
"pamRuleName": null,
"decisionSource": null
}
],
"pagination": { "page": 1, "limit": 50, "total": 1 }
}

decisionSource is derived: software_policy, pam_rule, human, or null (still pending). AI tool-action rows additionally carry toolName, riskTier, and actionDigest.

GET /pam/active

Returns up to 500 elevations whose status is approved, auto_approved, or actuating and whose expiresAt is in the future. No query parameters. Used to render the live “active windows” view with expiry countdowns.

{ "success": true, "active": [ { "id": "", "expiresAt": "2026-06-13T14:17:00Z", "…": "" } ] }

POST /pam/elevation-requests/:id/assertion-challenge — requires devices:execute.

Issues a short-lived (120s) WebAuthn assertion challenge bound to the elevation request and the caller, so the approving technician can satisfy a platform authenticator (Windows Hello / Touch ID) step-up before calling Respond. allowCredentials lists the caller’s active platform approver devices; if none are registered, the options carry no allowCredentials and the console falls back to a plain session-tap (L1) approval — this step is opt-in.

{ "options": { "challenge": "", "allowCredentials": [ { "id": "", "type": "public-key" } ], "…": "" } }

The resulting WebAuthn assertion is then passed as proof on the Respond call below.

POST /pam/elevation-requests/:id/respond — requires pam:approve + MFA.

Request body:

{
"decision": "approve",
"reason": "Approved per change CHG-1042",
"durationMinutes": 30
}
Field Type Notes
decision enum approve or deny (required)
reason string Optional on approve, recommended on deny; max 2000 chars
durationMinutes int Approval window, 1–1440 (24h max). Defaults to 15
proof object Optional WebAuthn assertion (from Assertion challenge above) satisfying a step-up approval. Absent → plain session-tap (L1) approval. Present but invalid → 401, never a silent downgrade. When partner policy enforces step-up, an under-assured approve is rejected with 403 (a deny is never blocked)
reauthPassword string Critical-tier (L4) re-authentication: the technician’s current account password, verified server-side. Only required when the elevation’s tier demands it
reauthMfaCode string Login-MFA (TOTP) fallback for SSO-only / passwordless accounts that can’t supply reauthPassword, verified server-side

Only a pending request can be decided. The transition is an atomic compare-and-swap on pending, so concurrent decisions are safe. A missing reauthPassword/reauthMfaCode on a critical approve that requires one returns 401 with code: "reauth_required" so the client can retry with the factor supplied.

{ "success": true, "id": "", "status": "approved", "enforcementStatus": "pending_dispatch" }

enforcementStatus reflects the device-side actuation of the approval (see Device actuation) — e.g. pending_dispatch once approved, progressing to active as the agent applies it.

Status Meaning
200 { "success": true, "id": "…", "status": "approved" | "denied", "enforcementStatus": "…" }
400 Invalid id or body
401 Invalid proof, or reauthPassword/reauthMfaCode missing or incorrect for a critical approve (code: "reauth_required")
403 Site access denied, MFA not satisfied, or an under-assured approve rejected by partner step-up policy
404 Request not found (or outside the caller’s org)
409 Request is no longer pending (lost the race), or a linked AI tool execution already timed out

POST /pam/elevation-requests/:id/revoke — requires pam:approve + MFA.

Ends an active elevation early.

{ "reason": "Maintenance window cancelled" }

reason is required (1–2000 chars). Only an active elevation (approved, auto_approved, or actuating) can be revoked; a stale or already-finished row returns 409.

{ "success": true, "id": "", "status": "revoked", "enforcementStatus": "cleanup_pending" }

enforcementStatus is always cleanup_pending on a successful revoke — the agent-side cleanup is dispatched asynchronously (see Device actuation); the time-bound expiry enforcer is the safety net if cleanup doesn’t confirm.


Method Path Description
GET /pam/rules List rules in priority order.
POST /pam/rules Create a rule.
POST /pam/rules/preview Dry-run draft criteria against request history.
PATCH /pam/rules/:id Update a rule.
DELETE /pam/rules/:id Delete a rule.

POST /pam/rules (create) and PATCH /pam/rules/:id (partial update) — require pam:manage_policy + MFA.

{
"name": "Auto-approve signed Mozilla installers",
"verdict": "auto_approve",
"priority": 50,
"enabled": true,
"matchSigner": "Mozilla Corporation",
"approvalDurationMinutes": 30,
"siteId": null
}
Field Type Notes
name string Required, 1–255 chars
verdict enum auto_approve, auto_deny, require_approval, ignore
priority int Lower runs first; default 100
enabled bool Default true
siteId uuid | null Narrow to one site; null = org-wide
matchSigner string Code-signer (executable rule). Mutually exclusive with matchSignerGroupId
matchSignerGroupId uuid | null Reference a reusable signer group instead of a single matchSigner (executable rule)
matchSignerThumbprint string | null SHA-256 hex thumbprint of the code-signing certificate (executable rule). Combinable with matchSigner, but mutually exclusive with matchSignerGroupId
matchHash string SHA-256 hex digest (executable rule)
matchPathGlob string File-path glob (executable rule)
matchParentImage string Parent process (executable rule)
matchCommandLine string Case-insensitive substring of the launched process command line (executable rule)
matchUser string Narrow by requesting user
matchAdGroup string Narrow by AD group
matchToolName string AI tool name (tool-action rule)
matchRiskTier int Risk tier 0–4, matched by exact equality (tool-action rule). See Risk-tier drift
matchNegate string[] | null Criterion keys the engine inverts (“does not match”). Valid keys: signer, signerGroup, hash, pathGlob, parentImage, commandLine, user, adGroup, toolName, riskTier
timeWindow object { start, end, days?, timezone? }, HH:MM 24h, days 0–6 (Sun–Sat)
approvalDurationMinutes int 1–1440; null falls back to the org default

Rule-shape validation (enforced on create and on the merged result of an update):

  • A rule must carry at least one identifying criterion (signer / signer group / hash / path / parent / command line / user / group / tool / tier). A rule scoped only by time window – or nothing – is rejected with 400.
  • A rule is either executable-shaped (signer / signer group / hash / path / parent / command line) or tool-action-shaped (tool name / risk tier). Mixing the two is rejected, because no single observation carries both.
  • matchSigner/matchSignerThumbprint and matchSignerGroupId are mutually exclusive – a rule references a signer (by name and/or thumbprint) or a signer group, not both.
  • A negated criterion never turns missing data into a match: if the observation didn’t report the value a negated criterion checks, the rule does not match.
  • verdict: "ignore" is not valid for tool-action rules – a tool action must be decided.
  • matchRiskTier must be a tier the rule’s tool selector can actually resolve to. A rule pinned to a tier no tool produces can never match, so create and update reject it with 400 and a body carrying code: "pam_rule_risk_tier_unreachable" plus a validTiers array of the tiers that selector currently resolves to. A rule naming a tool the API doesn’t recognise (an extension tool, for example) is not rejected – the check fails open rather than blocking a legitimate write.

Create returns 201 with the full rule; update returns 200.

Re-approving a suspended auto-approve rule

Section titled “Re-approving a suspended auto-approve rule”

The v0.112.0 upgrade switches every rule whose verdict was auto_approve to require_approval and keeps the original in a suspendedVerdict field, which GET /pam/rules returns alongside verdict ("verdict": "require_approval", "suspendedVerdict": "auto_approve"). To restore the original verdict:

PATCH /pam/rules/:id
{ "reapprove": true }

Requires pam:manage_policy + MFA. The rule’s verdict reverts to the suspended value, suspendedVerdict becomes null, and reapprovedAt / reapprovedByUserId are recorded. A reapprove body on a rule that is not suspended returns 400 "Rule is not suspended pending re-approval", and combining reapprove with an explicit verdict is rejected with 400. Editing a suspended rule’s verdict directly also clears suspendedVerdict, so a later reapprove cannot resurrect a stale verdict.

matchRiskTier is compared with exact equality, and a tool’s risk tier is decided by the API build, not by the rule. So when a release re-classifies a tool – as one did when three read-only execute_command command types moved from Tier 3 to Tier 2 – a stored rule written against the old tier silently stops covering those calls.

The failure direction is safe: an unmatched tool action falls through to the org’s default (pending / require approval) rather than auto-approving. But it is invisible, so the API surfaces it three ways:

  • On write – create and update reject a tier the selector can never resolve to (above).
  • At boot – the API scans enabled, tier-pinned rules on startup and logs a [pam-rule-tier-drift] warning naming every rule that can no longer match, alongside a Sentry event tagged pam_rule_risk_tier_unreachable. A deploy is the only moment a stored rule can go stale, so this runs once per start rather than on a schedule.
  • In the list – every row from GET /pam/rules carries matchRiskTierStale (bool) and matchRiskTierValidTiers (number[] | null), which the console renders as a warning badge on the rule.

A rule that merely narrowed is deliberately not flagged. A rule matching execute_command at Tier 3 still covers file_read and kill_process after the re-classification – it lost some coverage but is not dead, and many tools legitimately span several tiers. Only a tier the selector can never produce is reported.

Matching is not widened to “this tier or higher” on purpose: that would move the auto-approval boundary of every existing rule, which is a policy change rather than a bug fix.

POST /pam/rules/preview — requires devices:write.

Dry-runs draft criteria against historical requests so you can see what a rule would have matched before saving it. Accepts the same criteria fields as a rule, plus windowDays (1–90, default 30) and an optional flowType.

{
"success": true,
"totalMatched": 12,
"totalScanned": 480,
"windowDays": 30,
"truncated": false,
"statusBreakdown": { "pending": 2, "approved": 7, "auto_approved": 3, "denied": 0, "expired": 0, "revoked": 0, "actuating": 0 },
"sample": [ { "id": "", "requestedAt": "", "flowType": "uac_intercept", "status": "approved" } ]
}

The preview is a pure per-rule match – it does not replay priority shadowing or the software-policy bridge. Historical rows do not store AD groups, so any draft containing matchAdGroup reports zero matches.


A signer group is a named, reusable set of trusted code-signer (subject) patterns that executable rules can reference via matchSignerGroupId instead of repeating the same publisher across rules.

Method Path Description
GET /pam/signer-groups List the org’s signer groups.
POST /pam/signer-groups Create a signer group.
PATCH /pam/signer-groups/:id Partial update (name, description, signers).
DELETE /pam/signer-groups/:id Delete a group; 409 if it is still referenced by a rule.

Reads require devices:read; writes require devices:write + MFA.

{
"name": "Trusted browser vendors",
"description": "Publishers whose installers we auto-approve",
"signers": ["Mozilla Corporation", "Google LLC"]
}
Field Type Notes
name string Required, 1–255 chars
description string | null Optional, max 2000 chars
signers string[] Up to 500 trimmed entries, each max 255 chars; de-duplicated case-insensitively

GET and create/update return the full group(s):

{
"success": true,
"signerGroups": [
{
"id": "",
"orgId": "",
"name": "Trusted browser vendors",
"description": "Publishers whose installers we auto-approve",
"signers": ["Mozilla Corporation", "Google LLC"],
"createdAt": "",
"updatedAt": ""
}
]
}

Create returns 201. DELETE returns 409 (with a message naming the referencing-rule count) when the group is still used by a rule – detach those rules first.


The default verdict decides an elevation that matches no software policy and no PAM rule.

Method Path Description
GET /pam/config Get the org’s PAM config (devices:read).
PUT /pam/config Set the default unmatched verdict (devices:write + MFA).
{ "success": true, "config": { "orgId": "", "defaultUnmatchedVerdict": "require_approval" } }

defaultUnmatchedVerdict is require_approval (the default, also applied when no config row exists) or auto_deny. PUT accepts { "defaultUnmatchedVerdict": "require_approval" | "auto_deny" }.


Method Path Description
POST /devices/:id/actuate-elevation Queue an actuate_elevation command for an approved UAC elevation.

POST /devices/:id/actuate-elevation — organization/partner/system scope, devices:execute, MFA.

Queues the agent “go” signal for a consent.exe prompt that is already waiting on the user’s screen. The command payload that reaches the agent carries only the elevation request id and a timeout – the dormant-admin credential is minted locally by the agent and never crosses the wire.

{ "elevationRequestId": "", "timeoutMs": 8000 }

The route is single-use: it atomically transitions the request from approved to actuating in the same transaction that queues the command. A request that is not approved, or that another caller already claimed, is refused.

Status Meaning
201 Command queued; returns the command id and elevationRequestId
400 Invalid body, or device decommissioned
403 PAM actuator disabled, site access denied, or MFA not satisfied
404 Device or elevation request not found
409 Request not approved (wrong_status) or already being actuated (race_lost)

Method Path Description
POST /agents/:id/elevation-requests Agent reports an observed UAC consent prompt.

This endpoint is called by the agent, authenticated with the agent bearer token (not a user JWT), and is documented here for completeness. It records a UAC observation as an elevation_requests row with flowType: uac_intercept, runs the decisioning chain (software-policy bridge → PAM rules → org default verdict), and emits the corresponding event and audit rows. When the result is pending, Breeze additionally fans out a mobile approval request to each eligible technician and pushes it to their phones (best-effort — the 201 does not depend on push delivery; the first approver to decide on mobile wins and the siblings expire).

Request body (synthesized fields like reason are written server-side; the agent only sends discovery data):

{
"subject_username": "acme\\sarah",
"target_executable_path": "C:\\Users\\sarah\\Downloads\\FirefoxSetup.exe",
"target_executable_hash": "<agent-reported file hash>",
"target_executable_signer": "Mozilla Corporation",
"pid": 4812,
"parent_image": "C:\\Windows\\explorer.exe",
"command_line": "",
"observed_at": "2026-06-13T14:02:11Z"
}
Status Meaning
201 { "id": "…", "status": "pending" | "auto_approved" | "denied" }
200 { "id": null, "status": "ignored" } – an ignore rule matched; no request row is created
413 Body exceeds 32 KB
429 Per-device rate limit exceeded (about 10 requests/second)

Decisioning fails safe: if either evaluator errors, the request is recorded as pending rather than auto-approved, regardless of the org’s configured default verdict. When no policy and no rule match (without an error), the org default verdict decides — pending under require_approval, or denied under auto_deny.


Configuration policy: enabling UAC capture

Section titled “Configuration policy: enabling UAC capture”

Whether a device captures UAC prompts at all is governed by the pam configuration-policy feature, resolved through the normal policy hierarchy (partner → org → site → group → device, closest-wins). The feature’s inline settings shape is a single flag:

{ "uacInterceptionEnabled": false }

uacInterceptionEnabled defaults to false at every level – capture is opt-in, and admins turn it on for a scope by assigning a policy that sets it to true. This toggle controls only whether devices capture elevation prompts – it does not govern the rule chain, request queue, or audit history, which are scoped to the organization and site and managed in the Privileged Access console. See Configuration Policies for how feature settings resolve.