API reference
All routes are under /api/v1 unless shown. JSON is camelCase; unknown members are rejected (400 HW-REQUEST-006). Errors return { errorCode, message, correlationId, timestampUtc }. Request bodies are limited to 256 KB, except the App Control policy upload (policy XML up to 8 MiB).
| Method & route | Role | Purpose |
|---|---|---|
GET /health/live | anonymous | Process is up |
GET /health/ready | Operator | Auth and database working. Must be 401 without a token |
POST /agents/register | Agent | Register device, identity and capabilities |
POST /agents/heartbeat | Agent | Liveness; increasing sequence (409 on replay) |
POST /agents/messages/poll | Agent | Lease pending requests |
POST /agents/messages/result | Agent | Return a result |
POST /fleet/enrollment/redeem | Agent | Spend an enrollment secret |
GET /fleet/health | Operator | Fleet liveness summary |
GET /fleet/capabilities | Operator | What devices can answer |
GET /fleet/devices, /fleet/devices/{id} | Operator | Inventory; filters status, health, enrolled, name, tag, skip, take, after (the page’s nextCursor, for the name sorts; faster than skip on a large fleet); sort=name | name-desc | seen | seen-asc | version | tag | registered. tag matches the device tag, org unit or environment |
POST /fleet/devices/{id}/approve | disable | reinstate | revoke | Admin, or Manage devices on that device’s tag | Lifecycle; reason required for disable/revoke; revoke is permanent |
POST /fleet/requests, GET /fleet/requests/{id} | Operator | One operation on one device (202, then poll status) |
POST /fleet/queries, GET /fleet/queries/{id}?includeResults=true, POST /fleet/queries/{id}/cancel | Operator | Fan one operation out: AllCapableAgents, ExplicitAgents (max 100) or TaggedAgents with targetTag. savedQueryId runs a saved query; parameters (ad-hoc custom queries) needs Admin or Custom queries |
GET /fleet/queries?take=50 | Operator | Query history, newest first, with who ran each one (max 200) |
GET | POST /fleet/saved-queries, PUT | DELETE …/{id} | Operator | Saved queries. An operator changes only their own; custom queries (GetCustomCheckResults) need Admin or the Custom queries permission. 400 returns the validation reason |
GET /fleet/posture/baseline, GET /fleet/posture/baselines | Operator | The default baseline; or the default, every tag baseline and the tag values devices report |
PUT /fleet/posture/baseline, DELETE /fleet/posture/baseline?tag= | Admin or Baselines | Replace a baseline { tag?, version, settings: [{ key, expected, severity }] } (no tag = the default). Send the version you read (0 to create a tag baseline); a stale version is 400. At most 100 tag baselines |
GET /fleet/license, GET /fleet/license/features | Operator | License status (state, customer, dates, features, limits, binding, installation ID) and the feature catalog with what is included. Work in every license state |
POST /fleet/license/preview, PUT /fleet/license | Admin | { fileName, contentBase64, confirmSha256? }: check a .hwlic file without saving; import it (confirmSha256 must be the previewed SHA-256). 400 with the reason, 409 on a concurrent change |
POST /fleet/license/validate, GET /fleet/license/summary, GET /fleet/license/history | Admin | Verify now; plain-text summary for support; every imported license. All audited |
GET /fleet/scopes/me | Operator | The caller’s own permissions, device scope and the tags they can see; reach lists every permission with where it applies (Check my permissions) |
GET /fleet/scopes/directory?q= | Admin | Entra users and security groups whose name matches (needs Graph User.ReadBasic.All and GroupMember.Read.All on the API’s identity) |
GET | PUT /fleet/scopes, DELETE …/{id}, PUT …/settings | Admin | Access assignments (Entra group or user object id → tags (empty = every device) and permissions), and whether an unassigned operator sees no devices |
GET /fleet/posture?after=, GET /fleet/posture/{id} | Operator | Latest answer to each posture question per device (audited as PostureRead) |
GET /fleet/posture/trend?scope=&days=30 | Operator | Daily snapshot numbers, oldest first, for every device (no scope; needs every-device access) or one tag; also lists the tags with history. days 7–400, default 90 |
POST /fleet/posture/snapshot | Admin | Retake today’s snapshot (202; the worker takes it within about a minute) |
GET | POST /fleet/posture/views, DELETE …/{id} | Operator | Saved Posture views { name, report, view, tag, query, issue, area, shared }. Lists your own and shared views |
GET /fleet/posture/exports | Operator | Export files you may download (and, for administrators, the schedules) |
POST /fleet/posture/exports, PUT | DELETE …/{id}, POST …/{id}/run | Admin | Schedules { name, report: devices | baseline-devices | issues | baseline | trend, tag?, view?, frequency: Daily | Weekly, dayOfWeek, hourUtc, enabled }. run = produce one now (202) |
GET /fleet/posture/exports/files/{fileId} | Operator (file’s tag in scope) | Download the CSV; X-Content-SHA256 is the hash of the body |
POST /fleet/posture/refresh, POST /fleet/posture/{id}/refresh | Operator with Run queries | Ask every active device, or one, to refresh posture at its next check-in (202). 409 for a device that is not active |
POST /fleet/analysis/egress-grants | Operator | Authorize and audit sending a query bundle to an AI model. Requires tenant AllowModelEgress; bundleHash is a base64 SHA-256 |
POST | GET /fleet/enrollment-tokens, POST …/{id}/revoke | Admin, or Mint secrets (POST) / Enrollment (list, revoke) | Mint (secret shown once; a justification of 10–500 characters is required and recorded with who minted it), list, revoke |
POST | GET /fleet/certificate-authorities, POST …/{id}/disable | Admin | Trust anchors for client-certificate devices |
POST /fleet/device-identity/challenge, …/attest | anonymous, rate-limited | An Entra joined or hybrid joined device proves itself and receives its certificate. 404 HW-IDENT-001 when device identity is off |
POST /fleet/device-identity/renew | Agent (signed request) | Renew the device certificate; the agent does this at half-life |
GET | PUT /fleet/device-identity/settings, GET | POST …/authority | Admin or Enrollment (POST authority: Admin only) | Who may attest and the approval rule; the tenant’s device CA (POST creates it and proves the Key Vault key works) |
GET /fleet/devices/{id}/identity | Operator | How the device proved itself, its key protection and its current certificate |
GET /fleet/app-control/policies, …/{policyId}/versions, …/versions/{n}/content | Operator | Browse and download App Control policy versions |
POST /fleet/app-control/policies | Admin | Upload { xml, friendlyName?, notes? }. 201 new, 200 duplicate, 400 invalid, 409 conflict |
GET /fleet/activity, GET /fleet/activity/export | Admin | Activity log newest first (or CSV, up to 100,000 rows). Filters category=people|devices|service, actor, eventType, outcome=allowed|refused, device, from, to (yyyy-MM-dd UTC), after, limit (1–200) |
GET | PUT /fleet/app-control/management/settings, POST …/rebuild | Operator (GET); Admin (PUT, rebuild) | Module mode, capability switches and separation of duties { mode, capabilities, separationOfDuties? }. Modes above Recommend, or capabilities not built yet (Intune, generation, deployment, validation): 403 HW-APPCTL-001 |
GET /fleet/app-control/management/overview, …/policies/{policyId} | Operator | Fleet inventory and one policy’s versions and rollout by group, over the caller’s devices |
GET /fleet/app-control/management/devices, …/devices/{agentId} | Operator | Devices with findings; filters finding (a finding, any or not-reported), group, policy, name, after, limit; one device with expectations and peer gaps |
GET | PUT | DELETE /fleet/app-control/management/expected?scope= | Operator (GET); Admin or Baselines | Expected policy sets { scope, flagUnexpected, strategy: unspecified|allowList|denyList, policies: [{ policyId, name?, minimumVersion?, required: active|activeAndEnforcing|present|absent }] }; absent = must not be present (minimum version ignored) |
GET /fleet/app-control/management/fingerprints, …/fingerprints/{fingerprint}, …/fingerprints/compare?from=&to= | Operator | Policy sets with device counts and fragmented groups; one set’s content, breakdowns and explanation against each expected set; differences between two sets. 403 when no device in scope carries or carried it |
GET /fleet/app-control/management/alerts?open=&state=&type=&severity=&set=&after=, …/alerts/{id}; POST …/alerts/{id}/actions | Operator (scoped); actions: Admin or Baselines | Stateful App Control alerts with history and deliveries. Actions { action, note?, assignee?, dueAtUtc?, suppressUntilUtc?, concurrencyStamp }; 409 when the stamp is stale or the transition is not allowed |
GET /fleet/app-control/management/notifications, PUT | DELETE …/notifications/subscriptions, POST …/notifications/test, POST …/notifications/deliveries/{id}/retry | Admin | Configured channels (name, kind, host only), subscriptions, recent deliveries; test message; retry a given-up delivery |
GET /fleet/app-control/management/applications?search=&evidence=blocked|audited|installed&sort=blocked|audited|installed|recent|name&offset=&limit=, …/applications/{id} | Unrestricted operator, a Board permission, or Admin | Normalised application catalog (one row per application, never per hash) and one application with versions, install sources, events, devices (caller’s scope), groups and reviews |
POST /fleet/app-control/management/applications/{id}/merge | Admin | { intoApplicationId?, reason }: merge into another record, or separate with no target. Reason required; history kept |
GET | POST /fleet/app-control/management/imports | Admin (Advanced hunting on) | Recent imports; import an Advanced Hunting CSV { fileName, csv } (8 MB, 50,000 rows). Returns rows, accepted, duplicates, rejected and warnings |
GET /fleet/app-control/management/reviews?open=&due=&state=&search=&offset=&limit=, …/reviews/{id} | As for applications | Board reviews with counts by state and the caller’s Board permissions; one review with history and the actions open to the caller |
POST /fleet/app-control/management/reviews, POST …/reviews/{id}/actions | The action’s Board permission | Open { applicationId, text? }; act { action, text?, person?, outcome?, conditions?, expiresAtUtc?, recertifyAtUtc?, classification?, concurrencyStamp }. 403 with the reason when separation of duties refuses; 409 when the stamp is stale or the action is not available |
GET /fleet/app-control/management/reviews/{id}/rules | Any App Control governance permission; mode Recommend + Rule recommendations | Rule candidates and proposals with coverage, breadth and narrow risk, target policy, conflicts, known gaps, deny impact, notes and the caller’s available actions |
POST …/reviews/{id}/rules/builder/preview | Rule author (not the decision maker) | { main?, fallback?, overrides: [{ fileKey, level }] }. Read only: the rule builder’s steps, coverage, files with their rule, the policy XML and the matching New-CIPolicy command |
POST …/reviews/{id}/rules/builder | Rule author (not the decision maker) | Same body + concurrencyStamp (the review’s). Saves the built rule as a candidate and returns the rules with createdProposalId. 409 when stale or a rule is already proposed or approved |
POST /fleet/app-control/management/policy-packages/{id}/delete | Rule author or Rule approver (approved package: Rule approver) | { note?, resetApplications: [applicationId], concurrencyStamp }. Deletes the version and retires the approved rule of each listed application, sending its review back to Needs rule. 409 when stale or the package is in a deployment plan |
POST /fleet/app-control/management/rule-proposals/{id}/actions |
||
GET /fleet/app-control/management/rule-proposals/{id}/impact | Any App Control governance permission; mode Recommend + Rule recommendations + Policy simulation | The proposal, its newest simulation (latest: figures tagged Observed/Projected/Unknown, conflicts, critical components, installers, other applications, group and build breakdown, devices, missing/stale/not-evaluated data), current, earlier simulations, canSimulate, requiredForApproval. Devices outside your scope are left out of the lists |
POST …/rule-proposals/{id}/impact | Rule author or Rule approver | No body. Simulates a candidate, proposed or approved rule against recorded events and reported policies, stores it and records it in the review history. Changes no policy. 409 for a rejected, withdrawn or superseded rule; a repeat within 30 s returns the stored result |
GET /fleet/app-control/management/applications/{id}/files | As for applications; capability Application file scans | The application’s files from device scans (path, kind, version, signer, Authenticode SHA-256, issuer TBS hash, devices that block or would block), signers, latest scan per device, scanDevices (devices you may ask: agentId, deviceName, observedAtUtc; at most 200), rule kinds in the tenant’s order and the suggested plan. Scans of devices outside your scope are left out |
POST …/applications/{id}/files/scan | Run queries on each device asked | { agentIds? } (at most 5; default the 3 most recently seen with the application and FleetAppControlFileScanEnabled; pick from scanDevices of the GET). 409 when no device can answer or all are already scanning it |
GET /fleet/app-control/management/policy-packages, …/policy-packages/{id} | Any App Control governance permission; mode Generate + Policy package generation | One line per policy that can be built: each application’s own policy, merged policies and the deny base (lineKey, title, applications, approved rules, not yet packaged, no longer approved, newest version, newest approved version) and the newest 200 packages; one package with its XML, errors, warnings, changes and rules |
POST /fleet/app-control/management/policy-packages | Rule author | { kind: "supplemental"|"denyBase", basePolicyId?, lineKey?, applicationIds?, name?, auditMode? } (auditMode defaults to true; deny base only). lineKey rebuilds a line; otherwise one application in applicationIds builds its own policy, two or more (same base, at most 100) build a new merged policy, none builds every approved rule of the base in one policy. Each version holds every approved rule of its line. 409 when nothing is approved or nothing changed |
POST …/policy-packages/{id}/actions | Rule approver (not the builder) | { action: "approve"|"reject", note?, concurrencyStamp }. Reject needs note. Approve needs a validated package whose rules are all still approved and simulated |
GET …/policy-packages/{id}/export, …/manifest | Any App Control governance permission | Approved packages only (409 otherwise). XML as application/xml with X-Content-SHA256; manifest JSON with IDs, version, SHA-256, rules, approver and compile steps. Audited |
GET /fleet/app-control/management/program | Any App Control governance permission (or Admin); Application Control Board on | Counts (applications, approved, denied, exceptions, in review, not reviewed) and the Needs attention tasks: title, detail, count, link, who acts and whether you can |
GET /fleet/app-control/management/register | Any App Control governance permission (or Admin) | Decision register CSV (UTF-8 BOM, one row per application, max 50,000, formula-safe, no-store, export rate limit). Every download and refusal audited as AppControlRegisterExported |
GET /fleet/app-control/management/trend?scope=&days=, …/convergence | Operator (whole-fleet trend: Admin or unrestricted) | Daily App Control points (7–400 days); convergence per expected set. /devices also takes state, fingerprint, oscillating, set (set=(default) for the default set) |
Rate limits are per tenant and caller: 600 requests/minute overall, with tighter per-route limits on heartbeats (20/min), request submission (60), query submission (10), egress grants (20), enrollment (10) and export downloads (20). The anonymous device-identity routes allow 20 requests a minute per source address. Over the limit returns 429.