---
title: "Policies API v1"
description: "REST reference for the Policies service v1 API: policy reports, policy exceptions, and compliance."
diataxis: reference
applies_to:
  product: "nirmata-control-hub"
audience: ["platform-engineer","developer"]
last_updated: 2026-10-06
url: https://docs.nirmata.io/docs/reference/rest-api/policies_api_v1/
---

> **Applies to:** Nirmata Control Hub 4.0 and later

The Policies API v1 is a JSON REST API for the policy and compliance data in Nirmata Control Hub. Use it to read
policy reports and findings, manage policy exceptions and exception requests, and work with compliance standards,
controls, and compliance reports.

Unlike the model-based [platform API](../platform_api/), the v1 API uses fixed resource paths and plain
JSON request and response bodies. The [URL parameters](../platform_api/url_parameters/) (`fields`, `filter`, `query`, and so on)
and [common endpoints](../platform_api/common_endpoints/) do not apply to v1 endpoints.

## Base URL

All endpoints are served under:

```
https://<your-nirmata-host>/policies/api/v1
```

For Nirmata Control Hub SaaS, `<your-nirmata-host>` is `nirmata.io`.

## Authentication

Every request needs an `Authorization` header. Two schemes are supported:

```
Authorization: NIRMATA-API <api-token>
Authorization: Bearer <jwt>
```

To create an API token, see [API Tokens](/docs/control-hub/identity-access/api-keys/). Requests run as the user who
owns the token, scoped to that user's tenant. You never pass a tenant ID explicitly.

```bash
export NIRMATA_URL=https://nirmata.io
export NIRMATA_TOKEN=<api-token>

curl -s -H "Authorization: NIRMATA-API $NIRMATA_TOKEN" \
  "$NIRMATA_URL/policies/api/v1/policy-reports?limit=10"
```

## Roles

Access is controlled by the user roles in Nirmata Control Hub (see
[Users and Roles](/docs/control-hub/identity-access/users/)). Each endpoint lists the roles allowed to call it.
The table below summarizes the default permissions for the resources in this reference:

| Resource | `admin`, `platform` | `security` | `devops` |
|---|---|---|---|
| Policy reports and findings | Read, write, delete | Read, write, delete | Read |
| Compliance standards, controls, and reports | Read, write, delete | Read, write, delete | Read |
| Policy exceptions | Read, write, delete | Read, write, delete | Read, write, delete (own only) |
| Policy exception requests | Read, write, delete | Read, write, delete | Read, write, delete (own only) |

An endpoint's role list can be narrower than this table. For example, approving an exception request may be limited
to specific roles. A request from a user whose role is not allowed returns `403 Forbidden`.

## Pagination

List endpoints accept `limit` and `offset` query parameters and return a page envelope:

```json
{
  "items": [ ... ],
  "total": 132,
  "limit": 50,
  "offset": 0
}
```

| Parameter | Default | Description |
|---|---|---|
| `limit` | `50` | Maximum number of items to return. |
| `offset` | `0` | Number of items to skip. |

Some endpoints use a different envelope or extra filter parameters. Each endpoint's reference documents its own
response shape.

## Errors

Errors return a standard HTTP status code and a JSON body with an `error` message:

```json
{ "error": "Policy exception not found" }
```

| Status | Meaning |
|---|---|
| `400` | The request is malformed or failed validation. |
| `401`, `403` | The credentials are missing or invalid, or the caller's role is not allowed to perform the operation. |
| `404` | The resource does not exist in the caller's tenant. |
| `500` | Unexpected server error. |

## Endpoint groups

| Group | What it covers |
|---|---|
| [Policy Reports](policy_reports/) | Policy reports, scan findings, and publishing scan results |
| [Policy Exceptions](policy_exceptions/) | Kyverno policy exceptions and the resources they cover |
| [Policy Exception Requests](policy_exception_requests/) | Exception requests submitted for approval (read only) |
| [Compliance Standards](compliance_standards/) | Available standards, enablement, rescans, and custom standards |
| [Compliance Reports](compliance_reports/) | Compliance reports, control findings, history, and namespace compliance |
| [Compliance Scans](compliance_scans/) | Scheduled and on-demand compliance scans |
| [Compliance Audit Reports](compliance_audit_reports/) | Generating and downloading audit report artifacts |
| [Compliance Publishing](compliance_publishing/) | Publishing compliance snapshots and evidence packages |
| [Compliance Catalog](compliance_catalog/) | The compliance standards and controls catalog |


---

## Policy Reports


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

Read policy reports for clusters and namespaces, look up findings from scan runs, and publish scan results to Nirmata Control Hub.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [List policy reports](#list-policy-reports) | `GET` | `/policy-reports` |
| [Publish a report chunk](#publish-a-report-chunk) | `POST` | `/publishReportResult` |
| [Count policy reports](#count-policy-reports) | `GET` | `/policy-reports/count` |
| [Get policy report summary](#get-policy-report-summary) | `GET` | `/policy-reports/summary` |
| [Get a policy report](#get-a-policy-report) | `GET` | `/policy-reports/{id}` |
| [Get the active report run for a source](#get-the-active-report-run-for-a-source) | `GET` | `/report-results/active` |
| [List findings for a source](#list-findings-for-a-source) | `GET` | `/report-results/findings` |
| [List policy reports by cluster](#list-policy-reports-by-cluster) | `GET` | `/policy-reports/by-cluster/{clusterRef}` |
| [List policy reports by grade](#list-policy-reports-by-grade) | `GET` | `/policy-reports/by-grade/{grade}` |
| [List policy reports by namespace](#list-policy-reports-by-namespace) | `GET` | `/policy-reports/by-namespace/{namespace}` |
| [List findings for a scan](#list-findings-for-a-scan) | `GET` | `/report-results/reports/{scanId}/findings` |

## Reference

### List policy reports

```http
GET /policies/api/v1/policy-reports
```

Returns all Kubernetes policy reports stored for the tenant, paginated in memory with limit/offset.

**Roles:** `admin`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `limit` | `integer` | No | `50` | Maximum number of items to return |
| `offset` | `integer` | No | `0` | Number of items to skip |

**Response** `200` (`application/json`)

`Paginated: {items: PolicyReportDto[], total, limit, offset}`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy report ID |
| `kind` | `string` | Kubernetes kind (PolicyReport / ClusterPolicyReport) |
| `apiVersion` | `string` | Kubernetes API version |
| `uid` | `string` | Kubernetes UID of the report |
| `name` | `string` | Report name |
| `namespace` | `string` | Namespace (empty for cluster-scoped reports) |
| `resourceVersion` | `string` | Kubernetes resourceVersion |
| `summaryByCategory` | `object` | Result counts grouped by policy category |
| `grade` | `string` | Letter grade assigned to the report |
| `policyViolationCount` | `integer` | Number of policy violations |
| `yaml` | `string` | Raw report YAML |
| `clusterRef` | `object {id}` | Reference to the cluster the report came from |
| `results` | `array` | Always null in this API |
| `summary` | `object` | Always null in this API |
| `policyDetails` | `object` | Always null in this API |

**Errors**

- `500` — Failed to retrieve policy reports
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### Publish a report chunk

```http
POST /policies/api/v1/publishReportResult
```

Accepts one chunk of scan findings for a source and queues it for asynchronous processing. When all chunks for a scanId (totalFindings) are received, the new results replace the source's active report. Returns 202 immediately.

**Roles:** `admin`, `platform`, `security`

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `scanId` | `string` | Yes | Scan ID shared by all chunks of one scan |
| `totalFindings` | `integer` | Yes | Total findings across all chunks (&gt; 0, same on every chunk) |
| `labels` | `object` | Yes | Source labels; must include policies.nirmata.io/source-id |
| `findings` | `object[]` | No | Findings in this chunk |

**Response** `202` (`application/json`)

`{status: 'accepted', scanId}`

| Field | Type | Description |
|---|---|---|
| `status` | `string` | accepted |
| `scanId` | `string` | — |

**Errors**

- `400` — scanId missing, totalFindings <= 0, labels missing, or source-id label missing
- `401` — Tenant could not be resolved
- `500` — Failed to queue chunk
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

**Example**

```json
{"scanId":"scan-123","totalFindings":2,"labels":{"policies.nirmata.io/source-id":"my-repo"},"findings":[{...},{...}]}
```

### Count policy reports

```http
GET /policies/api/v1/policy-reports/count
```

Returns the number of policy reports for the tenant.

**Roles:** `admin`

**Response** `200` (`application/json`)

`{count}`

| Field | Type | Description |
|---|---|---|
| `count` | `integer` | Number of policy reports |

**Errors**

- `500` — Count failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### Get policy report summary

```http
GET /policies/api/v1/policy-reports/summary
```

Returns the total number of policy reports and the sum of their policy violation counts.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Response** `200` (`application/json`)

`{totalReports, totalViolations}`

| Field | Type | Description |
|---|---|---|
| `totalReports` | `integer` | Number of policy reports |
| `totalViolations` | `integer` | Sum of policyViolationCount across reports |

**Errors**

- `500` — Summary failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### Get a policy report

```http
GET /policies/api/v1/policy-reports/{id}
```

Returns a single policy report by ID.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Policy report ID |

**Response** `200` (`application/json`)

PolicyReportDto

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy report ID |
| `kind` | `string` | Kubernetes kind (PolicyReport / ClusterPolicyReport) |
| `apiVersion` | `string` | Kubernetes API version |
| `uid` | `string` | Kubernetes UID of the report |
| `name` | `string` | Report name |
| `namespace` | `string` | Namespace (empty for cluster-scoped reports) |
| `resourceVersion` | `string` | Kubernetes resourceVersion |
| `summaryByCategory` | `object` | Result counts grouped by policy category |
| `grade` | `string` | Letter grade assigned to the report |
| `policyViolationCount` | `integer` | Number of policy violations |
| `yaml` | `string` | Raw report YAML |
| `clusterRef` | `object {id}` | Reference to the cluster the report came from |
| `results` | `array` | Always null in this API |
| `summary` | `object` | Always null in this API |
| `policyDetails` | `object` | Always null in this API |

**Errors**

- `404` — Policy report not found
- `500` — Lookup failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### Get the active report run for a source

```http
GET /policies/api/v1/report-results/active
```

Returns metadata for the currently active scan (report run) of the given source.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `sourceId` | `string` | Yes | — | Source identifier |

**Response** `200` (`application/json`)

ReportRun object

| Field | Type | Description |
|---|---|---|
| `scanId` | `string` | Scan (report run) ID |
| `tenantId` | `string` | — |
| `sourceId` | `string` | Source the scan belongs to |
| `sourceType` | `string` | — |
| `status` | `string` | Run status (ACTIVE for this endpoint) |
| `totalFindings` | `integer` | Expected findings |
| `receivedFindings` | `integer` | Findings received so far |
| `createdAt` | `string` | — |
| `updatedAt` | `string` | — |
| `finalizedAt` | `string` | — |
| `archivedAt` | `string` | — |
| `labels` | `object` | Source labels |

**Errors**

- `400` — sourceId missing
- `401` — Tenant could not be resolved
- `404` — No active scan for this source
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List findings for a source

```http
GET /policies/api/v1/report-results/findings
```

Returns paginated findings from the active scan of a source, optionally filtered by result. Ordered by finding ID.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `sourceId` | `string` | Yes | — | Source identifier |
| `result` | `string` | No | — | Only return findings with this result (e.g. fail) |
| `limit` | `integer` | No | `500` | Page size |
| `offset` | `integer` | No | `0` | Items to skip (&gt;= 0) |

**Response** `200` (`application/json`)

`{total, count, offset, findings: Finding[]}`

| Field | Type | Description |
|---|---|---|
| `total` | `integer` | Total matching findings |
| `count` | `integer` | Findings in this page |
| `offset` | `integer` | — |
| `scanId` | `string` | — |
| `sourceId` | `string` | — |
| `result` | `string` | Finding result, e.g. pass/fail |
| `message` | `string` | — |
| `severity` | `string` | — |
| `description` | `string` | — |
| `timestamp` | `string` | — |
| `target` | `object {type,name,metadata}` | Scanned target |
| `policy` | `object {name,rule,kind,apiGroup}` | Policy that produced the finding |
| `category` | `array/object` | Categories |

**Errors**

- `400` — sourceId missing or offset < 0
- `401` — Tenant could not be resolved
- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

**Example**

```json
GET /policies/api/v1/report-results/findings?sourceId=my-cluster&result=fail&limit=100
```

### List policy reports by cluster

```http
GET /policies/api/v1/policy-reports/by-cluster/{clusterRef}
```

Returns all policy reports for the tenant whose cluster matches the given value. Not paginated; returns an empty array if none match.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `clusterRef` | `string` | Cluster ID |

**Response** `200` (`application/json`)

`PolicyReportDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy report ID |
| `kind` | `string` | Kubernetes kind (PolicyReport / ClusterPolicyReport) |
| `apiVersion` | `string` | Kubernetes API version |
| `uid` | `string` | Kubernetes UID of the report |
| `name` | `string` | Report name |
| `namespace` | `string` | Namespace (empty for cluster-scoped reports) |
| `resourceVersion` | `string` | Kubernetes resourceVersion |
| `summaryByCategory` | `object` | Result counts grouped by policy category |
| `grade` | `string` | Letter grade assigned to the report |
| `policyViolationCount` | `integer` | Number of policy violations |
| `yaml` | `string` | Raw report YAML |
| `clusterRef` | `object {id}` | Reference to the cluster the report came from |
| `results` | `array` | Always null in this API |
| `summary` | `object` | Always null in this API |
| `policyDetails` | `object` | Always null in this API |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List policy reports by grade

```http
GET /policies/api/v1/policy-reports/by-grade/{grade}
```

Returns all policy reports for the tenant whose grade matches the given value. Not paginated; returns an empty array if none match.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `grade` | `string` | Grade value (exact match, e.g. A-F) |

**Response** `200` (`application/json`)

`PolicyReportDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy report ID |
| `kind` | `string` | Kubernetes kind (PolicyReport / ClusterPolicyReport) |
| `apiVersion` | `string` | Kubernetes API version |
| `uid` | `string` | Kubernetes UID of the report |
| `name` | `string` | Report name |
| `namespace` | `string` | Namespace (empty for cluster-scoped reports) |
| `resourceVersion` | `string` | Kubernetes resourceVersion |
| `summaryByCategory` | `object` | Result counts grouped by policy category |
| `grade` | `string` | Letter grade assigned to the report |
| `policyViolationCount` | `integer` | Number of policy violations |
| `yaml` | `string` | Raw report YAML |
| `clusterRef` | `object {id}` | Reference to the cluster the report came from |
| `results` | `array` | Always null in this API |
| `summary` | `object` | Always null in this API |
| `policyDetails` | `object` | Always null in this API |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List policy reports by namespace

```http
GET /policies/api/v1/policy-reports/by-namespace/{namespace}
```

Returns all policy reports for the tenant whose namespace matches the given value. Not paginated; returns an empty array if none match.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `namespace` | `string` | Namespace name (exact match) |

**Response** `200` (`application/json`)

`PolicyReportDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy report ID |
| `kind` | `string` | Kubernetes kind (PolicyReport / ClusterPolicyReport) |
| `apiVersion` | `string` | Kubernetes API version |
| `uid` | `string` | Kubernetes UID of the report |
| `name` | `string` | Report name |
| `namespace` | `string` | Namespace (empty for cluster-scoped reports) |
| `resourceVersion` | `string` | Kubernetes resourceVersion |
| `summaryByCategory` | `object` | Result counts grouped by policy category |
| `grade` | `string` | Letter grade assigned to the report |
| `policyViolationCount` | `integer` | Number of policy violations |
| `yaml` | `string` | Raw report YAML |
| `clusterRef` | `object {id}` | Reference to the cluster the report came from |
| `results` | `array` | Always null in this API |
| `summary` | `object` | Always null in this API |
| `policyDetails` | `object` | Always null in this API |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List findings for a scan

```http
GET /policies/api/v1/report-results/reports/{scanId}/findings
```

Returns paginated findings for a specific scan, optionally filtered by policy name. No total count is returned.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `scanId` | `string` | Scan ID |

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `policyName` | `string` | No | — | Only return findings for this policy |
| `limit` | `integer` | No | `500` | Page size |
| `offset` | `integer` | No | `0` | Items to skip (&gt;= 0) |

**Response** `200` (`application/json`)

`{scanId, count, findings: Finding[]}`

| Field | Type | Description |
|---|---|---|
| `scanId` | `string` | — |
| `count` | `integer` | — |
| `scanId` | `string` | — |
| `sourceId` | `string` | — |
| `result` | `string` | Finding result, e.g. pass/fail |
| `message` | `string` | — |
| `severity` | `string` | — |
| `description` | `string` | — |
| `timestamp` | `string` | — |
| `target` | `object {type,name,metadata}` | Scanned target |
| `policy` | `object {name,rule,kind,apiGroup}` | Policy that produced the finding |
| `category` | `array/object` | Categories |

**Errors**

- `400` — offset < 0
- `401` — Tenant could not be resolved
- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation


---

## Policy Exceptions


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

Read the Kyverno policy exceptions that Nirmata Control Hub tracks across your clusters, and the resources each exception covers.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [List policy exceptions](#list-policy-exceptions) | `GET` | `/policy-exceptions` |
| [Count policy exceptions](#count-policy-exceptions) | `GET` | `/policy-exceptions/count` |
| [Find policy exceptions for a policy on a cluster](#find-policy-exceptions-for-a-policy-on-a-cluster) | `GET` | `/policy-exceptions/search` |
| [Get policy exception summary](#get-policy-exception-summary) | `GET` | `/policy-exceptions/summary` |
| [Get a policy exception](#get-a-policy-exception) | `GET` | `/policy-exceptions/{id}` |
| [List policy exceptions for a cluster](#list-policy-exceptions-for-a-cluster) | `GET` | `/policy-exceptions/by-cluster/{clusterId}` |
| [List policy exceptions by kind](#list-policy-exceptions-by-kind) | `GET` | `/policy-exceptions/by-kind/{kind}` |
| [List policy exceptions in a namespace](#list-policy-exceptions-in-a-namespace) | `GET` | `/policy-exceptions/by-namespace/{namespace}` |
| [List resources excepted by a policy exception](#list-resources-excepted-by-a-policy-exception) | `GET` | `/policy-exceptions/{id}/excepted-resources` |

## Reference

### List policy exceptions

```http
GET /policies/api/v1/policy-exceptions
```

Returns all Kyverno policy exceptions in the tenant, paginated in memory with limit/offset.

**Roles:** `admin`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `limit` | `integer` | No | `50` | Maximum items to return |
| `offset` | `integer` | No | `0` | Number of items to skip |

**Response** `200` (`application/json`)

`Paginated: {items: PolicyException[], total, limit, offset}`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception ID |
| `apiVersion` | `string` | Kyverno API version of the PolicyException resource |
| `kind` | `string` | Resource kind (e.g. PolicyException) |
| `name` | `string` | Policy exception name |
| `namespace` | `string` | Kubernetes namespace of the exception |
| `uid` | `string` | Kubernetes UID |
| `resourceVersion` | `string` | Kubernetes resource version |
| `clusterRef` | `object` | Reference to the cluster: {service, modelIndex, id} |
| `exceptions` | `array<object>` | Excepted policies/rules: {policyName, ruleNames, namespace, policyUID, kind, policyRef} |
| `exceptedResources` | `array<object>` | Resources currently excepted: {apiVersion, kind, name, namespace, fieldPath, resourceVersion, uid, resourceRef} |
| `yaml` | `string` | Full YAML of the PolicyException resource |
| `requestDetails` | `string` | JSON-encoded string with details of the originating exception request, if any |

**Errors**

- `500` — Failed to retrieve policy exceptions
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### Count policy exceptions

```http
GET /policies/api/v1/policy-exceptions/count
```

Returns the total number of policy exceptions in the tenant.

**Roles:** `admin`

**Response** `200` (`application/json`)

`{count: integer}`

| Field | Type | Description |
|---|---|---|
| `count` | `integer` | Number of policy exceptions |

**Errors**

- `500` — Failed to count policy exceptions
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### Find policy exceptions for a policy on a cluster

```http
GET /policies/api/v1/policy-exceptions/search
```

Returns lightweight records of policy exceptions on a cluster that except the named policy, optionally restricted to a namespace.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `clusterId` | `string` | Yes | — | Cluster ID |
| `policy` | `string` | Yes | — | Exact policy name to match in the exception's policy list |
| `namespace` | `string` | No | — | Restrict to exceptions in this namespace |

**Response** `200` (`application/json`)

`Array of {id, name, namespace}`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception ID |
| `name` | `string` | Name (empty string if unset) |
| `namespace` | `string` | Namespace (empty string if unset) |

**Errors**

- `400` — clusterId or policy query parameter missing
- `500` — Failed to search policy exceptions
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

**Example**

```bash
curl -H 'Authorization: NIRMATA-API <token>' 'https://<host>/policies/api/v1/policy-exceptions/search?clusterId=<id>&policy=disallow-privileged-containers'
```

### Get policy exception summary

```http
GET /policies/api/v1/policy-exceptions/summary
```

Returns counts of policy exceptions in the tenant: total, those with excepted resources, those listing policies, and empty ones.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Response** `200` (`application/json`)

`{total, withExceptedResources, withPolicyExceptions, empty}`

| Field | Type | Description |
|---|---|---|
| `total` | `integer` | Total policy exceptions |
| `withExceptedResources` | `integer` | Exceptions that currently match at least one resource |
| `withPolicyExceptions` | `integer` | Exceptions that list at least one policy |
| `empty` | `integer` | Approximate count of exceptions without policies/resources |

**Errors**

- `500` — Failed to get policy exceptions summary
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### Get a policy exception

```http
GET /policies/api/v1/policy-exceptions/{id}
```

Returns a single policy exception by ID. Users with own-scope access can only read exceptions they requested.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception ID |

**Response** `200` (`application/json`)

PolicyException

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception ID |
| `apiVersion` | `string` | Kyverno API version of the PolicyException resource |
| `kind` | `string` | Resource kind (e.g. PolicyException) |
| `name` | `string` | Policy exception name |
| `namespace` | `string` | Kubernetes namespace of the exception |
| `uid` | `string` | Kubernetes UID |
| `resourceVersion` | `string` | Kubernetes resource version |
| `clusterRef` | `object` | Reference to the cluster: {service, modelIndex, id} |
| `exceptions` | `array<object>` | Excepted policies/rules: {policyName, ruleNames, namespace, policyUID, kind, policyRef} |
| `exceptedResources` | `array<object>` | Resources currently excepted: {apiVersion, kind, name, namespace, fieldPath, resourceVersion, uid, resourceRef} |
| `yaml` | `string` | Full YAML of the PolicyException resource |
| `requestDetails` | `string` | JSON-encoded string with details of the originating exception request, if any |

**Errors**

- `404` — Policy exception not found
- `500` — Failed to retrieve policy exception
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### List policy exceptions for a cluster

```http
GET /policies/api/v1/policy-exceptions/by-cluster/{clusterId}
```

Returns all policy exceptions belonging to the given cluster. Not paginated.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `clusterId` | `string` | Cluster ID |

**Response** `200` (`application/json`)

`PolicyException[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception ID |
| `apiVersion` | `string` | Kyverno API version of the PolicyException resource |
| `kind` | `string` | Resource kind (e.g. PolicyException) |
| `name` | `string` | Policy exception name |
| `namespace` | `string` | Kubernetes namespace of the exception |
| `uid` | `string` | Kubernetes UID |
| `resourceVersion` | `string` | Kubernetes resource version |
| `clusterRef` | `object` | Reference to the cluster: {service, modelIndex, id} |
| `exceptions` | `array<object>` | Excepted policies/rules: {policyName, ruleNames, namespace, policyUID, kind, policyRef} |
| `exceptedResources` | `array<object>` | Resources currently excepted: {apiVersion, kind, name, namespace, fieldPath, resourceVersion, uid, resourceRef} |
| `yaml` | `string` | Full YAML of the PolicyException resource |
| `requestDetails` | `string` | JSON-encoded string with details of the originating exception request, if any |

**Errors**

- `500` — Lookup failed
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### List policy exceptions by kind

```http
GET /policies/api/v1/policy-exceptions/by-kind/{kind}
```

Returns all policy exceptions whose resource kind exactly matches the given value (e.g. PolicyException). Not paginated.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `kind` | `string` | Resource kind |

**Response** `200` (`application/json`)

`PolicyException[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception ID |
| `apiVersion` | `string` | Kyverno API version of the PolicyException resource |
| `kind` | `string` | Resource kind (e.g. PolicyException) |
| `name` | `string` | Policy exception name |
| `namespace` | `string` | Kubernetes namespace of the exception |
| `uid` | `string` | Kubernetes UID |
| `resourceVersion` | `string` | Kubernetes resource version |
| `clusterRef` | `object` | Reference to the cluster: {service, modelIndex, id} |
| `exceptions` | `array<object>` | Excepted policies/rules: {policyName, ruleNames, namespace, policyUID, kind, policyRef} |
| `exceptedResources` | `array<object>` | Resources currently excepted: {apiVersion, kind, name, namespace, fieldPath, resourceVersion, uid, resourceRef} |
| `yaml` | `string` | Full YAML of the PolicyException resource |
| `requestDetails` | `string` | JSON-encoded string with details of the originating exception request, if any |

**Errors**

- `500` — Lookup failed
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### List policy exceptions in a namespace

```http
GET /policies/api/v1/policy-exceptions/by-namespace/{namespace}
```

Returns all policy exceptions whose namespace exactly matches the given value. Not paginated.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `namespace` | `string` | Kubernetes namespace name |

**Response** `200` (`application/json`)

`PolicyException[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception ID |
| `apiVersion` | `string` | Kyverno API version of the PolicyException resource |
| `kind` | `string` | Resource kind (e.g. PolicyException) |
| `name` | `string` | Policy exception name |
| `namespace` | `string` | Kubernetes namespace of the exception |
| `uid` | `string` | Kubernetes UID |
| `resourceVersion` | `string` | Kubernetes resource version |
| `clusterRef` | `object` | Reference to the cluster: {service, modelIndex, id} |
| `exceptions` | `array<object>` | Excepted policies/rules: {policyName, ruleNames, namespace, policyUID, kind, policyRef} |
| `exceptedResources` | `array<object>` | Resources currently excepted: {apiVersion, kind, name, namespace, fieldPath, resourceVersion, uid, resourceRef} |
| `yaml` | `string` | Full YAML of the PolicyException resource |
| `requestDetails` | `string` | JSON-encoded string with details of the originating exception request, if any |

**Errors**

- `500` — Lookup failed
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### List resources excepted by a policy exception

```http
GET /policies/api/v1/policy-exceptions/{id}/excepted-resources
```

Returns the Kubernetes resources currently matched (excepted) by the given policy exception.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception ID |

**Response** `200` (`application/json`)

`{exceptedResources: object[], count: integer}`

| Field | Type | Description |
|---|---|---|
| `exceptedResources` | `array<object>` | {apiVersion, kind, name, namespace, fieldPath, resourceVersion, uid, resourceRef} |
| `count` | `integer` | Number of excepted resources |

**Errors**

- `404` — Policy exception not found
- `500` — Failed to get excepted resources
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted


---

## Policy Exception Requests


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

Read policy exception requests submitted for approval. The v1 API currently provides read access only. Create, approve, and reject exception requests from the Nirmata Control Hub console.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [List policy exception requests](#list-policy-exception-requests) | `GET` | `/policy-exception-requests` |
| [Count policy exception requests](#count-policy-exception-requests) | `GET` | `/policy-exception-requests/count` |
| [List pending exception requests](#list-pending-exception-requests) | `GET` | `/policy-exception-requests/pending` |
| [Get a policy exception request](#get-a-policy-exception-request) | `GET` | `/policy-exception-requests/{id}` |
| [List exception requests for a cluster](#list-exception-requests-for-a-cluster) | `GET` | `/policy-exception-requests/by-cluster/{clusterId}` |
| [List exception requests by requester](#list-exception-requests-by-requester) | `GET` | `/policy-exception-requests/by-requester/{requestedBy}` |
| [List exception requests by state](#list-exception-requests-by-state) | `GET` | `/policy-exception-requests/by-status/{status}` |

## Reference

### List policy exception requests

```http
GET /policies/api/v1/policy-exception-requests
```

Returns all policy exception requests in the tenant, paginated in memory with limit/offset.

**Roles:** `admin`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `limit` | `integer` | No | `50` | Maximum items to return |
| `offset` | `integer` | No | `0` | Number of items to skip |

**Response** `200` (`application/json`)

`Paginated: {items: PolicyExceptionRequest[], total, limit, offset}`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Request ID |
| `name` | `string` | Request name |
| `requestedBy` | `string` | User who submitted the request |
| `requestedByEmail` | `string` | Email of the requester |
| `status` | `string` | Request state: pendingApproval \| approved \| rejected |
| `requestType` | `string` | Type of exception request |
| `justification` | `string` | Reason given by the requester |

**Errors**

- `500` — Failed to retrieve policy exception requests
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### Count policy exception requests

```http
GET /policies/api/v1/policy-exception-requests/count
```

Returns the total number of policy exception requests in the tenant.

**Roles:** `admin`

**Response** `200` (`application/json`)

`{count: integer}`

| Field | Type | Description |
|---|---|---|
| `count` | `integer` | Number of requests |

**Errors**

- `500` — Failed to count requests
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### List pending exception requests

```http
GET /policies/api/v1/policy-exception-requests/pending
```

Returns all requests awaiting approval (state pendingApproval). Not paginated.

**Roles:** `admin`

**Response** `200` (`application/json`)

`PolicyExceptionRequest[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Request ID |
| `name` | `string` | Request name |
| `requestedBy` | `string` | User who submitted the request |
| `requestedByEmail` | `string` | Email of the requester |
| `status` | `string` | Request state: pendingApproval \| approved \| rejected |
| `requestType` | `string` | Type of exception request |
| `justification` | `string` | Reason given by the requester |

**Errors**

- `500` — Failed to get pending requests
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### Get a policy exception request

```http
GET /policies/api/v1/policy-exception-requests/{id}
```

Returns a single policy exception request by ID.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Policy exception request ID |

**Response** `200` (`application/json`)

PolicyExceptionRequest

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Request ID |
| `name` | `string` | Request name |
| `requestedBy` | `string` | User who submitted the request |
| `requestedByEmail` | `string` | Email of the requester |
| `status` | `string` | Request state: pendingApproval \| approved \| rejected |
| `requestType` | `string` | Type of exception request |
| `justification` | `string` | Reason given by the requester |

**Errors**

- `404` — Request not found
- `500` — Failed to retrieve request
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### List exception requests for a cluster

```http
GET /policies/api/v1/policy-exception-requests/by-cluster/{clusterId}
```

Returns requests that target the given cluster. Not paginated.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `clusterId` | `string` | Cluster ID |

**Response** `200` (`application/json`)

`PolicyExceptionRequest[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Request ID |
| `name` | `string` | Request name |
| `requestedBy` | `string` | User who submitted the request |
| `requestedByEmail` | `string` | Email of the requester |
| `status` | `string` | Request state: pendingApproval \| approved \| rejected |
| `requestType` | `string` | Type of exception request |
| `justification` | `string` | Reason given by the requester |

**Errors**

- `500` — Lookup failed
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### List exception requests by requester

```http
GET /policies/api/v1/policy-exception-requests/by-requester/{requestedBy}
```

Returns requests submitted by the given user. Not paginated.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `requestedBy` | `string` | Requester identifier, matched exactly against the stored requestedBy value |

**Response** `200` (`application/json`)

`PolicyExceptionRequest[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Request ID |
| `name` | `string` | Request name |
| `requestedBy` | `string` | User who submitted the request |
| `requestedByEmail` | `string` | Email of the requester |
| `status` | `string` | Request state: pendingApproval \| approved \| rejected |
| `requestType` | `string` | Type of exception request |
| `justification` | `string` | Reason given by the requester |

**Errors**

- `500` — Lookup failed
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted

### List exception requests by state

```http
GET /policies/api/v1/policy-exception-requests/by-status/{status}
```

Returns requests whose state exactly matches the given value. Not paginated.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `status` | `string` | Request state: pendingApproval, approved or rejected (case-sensitive) |

**Response** `200` (`application/json`)

`PolicyExceptionRequest[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Request ID |
| `name` | `string` | Request name |
| `requestedBy` | `string` | User who submitted the request |
| `requestedByEmail` | `string` | Email of the requester |
| `status` | `string` | Request state: pendingApproval \| approved \| rejected |
| `requestType` | `string` | Type of exception request |
| `justification` | `string` | Reason given by the requester |

**Errors**

- `500` — Lookup failed
- `401` — Missing or invalid credentials
- `403` — Caller's role is not permitted


---

## Compliance Standards


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

List the compliance standards available to your tenant, enable or disable them for clusters, trigger rescans, and manage custom standards that map Kyverno policies to your own controls.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [List custom compliance standards](#list-custom-compliance-standards) | `GET` | `/compliance/kyverno/custom-mappings` |
| [Create a custom compliance standard](#create-a-custom-compliance-standard) | `POST` | `/compliance/kyverno/custom-mappings` |
| [List compliance standards](#list-compliance-standards) | `GET` | `/compliance/kyverno/standards` |
| [Download an example custom standard](#download-an-example-custom-standard) | `GET` | `/compliance/kyverno/custom-mappings/example` |
| [Download the custom standard JSON Schema](#download-the-custom-standard-json-schema) | `GET` | `/compliance/kyverno/custom-mappings/schema` |
| [Get a custom compliance standard](#get-a-custom-compliance-standard) | `GET` | `/compliance/kyverno/custom-mappings/{standardKey}` |
| [Update a custom compliance standard](#update-a-custom-compliance-standard) | `PUT` | `/compliance/kyverno/custom-mappings/{standardKey}` |
| [Delete a custom compliance standard](#delete-a-custom-compliance-standard) | `DELETE` | `/compliance/kyverno/custom-mappings/{standardKey}` |
| [List enabled compliance standards](#list-enabled-compliance-standards) | `GET` | `/compliance/kyverno/standards/enabled` |
| [Rescan all enabled standards](#rescan-all-enabled-standards) | `POST` | `/compliance/kyverno/standards/rescan` |
| [Get a standard's control catalog](#get-a-standards-control-catalog) | `GET` | `/compliance/kyverno/standards/{standard}/controls` |
| [Enable a standard for targets](#enable-a-standard-for-targets) | `PUT` | `/compliance/kyverno/standards/{standard}/enablement` |
| [Update standard enablement flags](#update-standard-enablement-flags) | `PATCH` | `/compliance/kyverno/standards/{standard}/enablement` |
| [Disable a compliance standard](#disable-a-compliance-standard) | `DELETE` | `/compliance/kyverno/standards/{standard}/enablement` |
| [Get a standard's policy-to-control mapping](#get-a-standards-policy-to-control-mapping) | `GET` | `/compliance/kyverno/standards/{standard}/mappings` |
| [Rescan one compliance standard](#rescan-one-compliance-standard) | `POST` | `/compliance/kyverno/standards/{standard}/rescan` |

## Reference

### List custom compliance standards

```http
GET /policies/api/v1/compliance/kyverno/custom-mappings
```

Lists the custom compliance standards uploaded by this tenant.

**Roles:** `admin`, `platform`, `security`, `devops`

**Response** `200` (`application/json`)

Array of custom standard summaries (not paginated)

| Field | Type | Description |
|---|---|---|
| `standardKey` | `string` | Unique key of the custom standard |
| `displayName` | `string` | — |
| `policyCount` | `integer` | Number of mapped policies |
| `controlCount` | `integer` | Distinct controls mapped |
| `adminState` | `string` | — |
| `createdBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `updatedAt` | `string (date-time)` | — |

**Errors**

- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Create a custom compliance standard

```http
POST /policies/api/v1/compliance/kyverno/custom-mappings
```

Uploads a YAML definition of a new custom standard. It is validated against the schema and then scanned, scored and reported exactly like a built-in standard. Non-blocking issues (e.g. policy names not yet managed in Nirmata) are returned in a warnings array.

**Roles:** `admin`, `security`

**Request body** (`text/yaml (also application/x-yaml, text/plain)`)

| Field | Type | Required | Description |
|---|---|---|---|
| `standardKey` | `string` | Yes | Lowercase letters, digits, hyphens; must not match a built-in standard |
| `displayName` | `string` | Yes | — |
| `preferVpol` | `boolean` | No | Informational |
| `sources` | `object[]` | No | {id, title, ref, effective} |
| `policies` | `object` | Yes | Map of Kyverno policy name to {clusterScoped, controls[]}; unknown policy names produce warnings, not errors |
| `non-automatable` | `object[]` | No | {id, family, reason} controls reported as not automatable |

**Response** `201` (`application/json`)

`Custom standard detail plus optional warnings: string[]`

| Field | Type | Description |
|---|---|---|
| `standardKey` | `string` | Unique key of the custom standard |
| `displayName` | `string` | — |
| `policyCount` | `integer` | Number of mapped policies |
| `controlCount` | `integer` | Distinct controls mapped |
| `adminState` | `string` | — |
| `createdBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `updatedAt` | `string (date-time)` | — |
| `preferVpol` | `boolean` | — |
| `sources` | `object[]` | {id, title, ref, effective} |
| `policies` | `object` | Map of policy name to {clusterScoped, controls} |
| `nonAutomatable` | `object[]` | {id, family, reason} |
| `rawYaml` | `string` | The uploaded YAML |
| `contentSha256` | `string` | — |
| `warnings` | `string[]` | Present only when there are warnings |

**Errors**

- `400` — YAML fails validation (code VALIDATION_FAILED)
- `409` — standardKey already exists for this tenant or collides with a built-in standard (code CONFLICT)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

**Example**

```bash
curl -X POST -H 'Content-Type: text/yaml' --data-binary @custom-acme-baseline.yaml .../compliance/kyverno/custom-mappings
```

### List compliance standards

```http
GET /policies/api/v1/compliance/kyverno/standards
```

Lists every compliance standard available to the tenant: built-in standards plus the tenant's custom standards. Does not require any reports to exist.

**Roles:** `admin`, `platform`, `security`, `devops`

**Response** `200` (`application/json`)

Array of ComplianceStandardDto

| Field | Type | Description |
|---|---|---|
| `standard` | `string` | Standard key |
| `standardDisplayName` | `string` | — |
| `custom` | `boolean` | True for tenant-uploaded standards |
| `type` | `string` | built-in \| industry \| custom |
| `domain` | `string[]` | Topical tags, e.g. kubernetes, identity-rbac, supply-chain |

**Errors**

- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Download an example custom standard

```http
GET /policies/api/v1/compliance/kyverno/custom-mappings/example
```

Returns a fully commented, schema-valid example custom standard YAML (custom-mapping.example.yaml) that can be edited and uploaded.

**Roles:** `admin`, `platform`, `security`, `devops`

**Response** `200` (`text/yaml`)

YAML document (attachment)

**Errors**

- `500` — Example resource unavailable
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Download the custom standard JSON Schema

```http
GET /policies/api/v1/compliance/kyverno/custom-mappings/schema
```

Returns the JSON Schema describing the YAML body accepted when creating or updating a custom compliance standard, as an attachment (custom-mapping.schema.json).

**Roles:** `admin`, `platform`, `security`, `devops`

**Response** `200` (`application/json`)

JSON Schema document (attachment)

**Errors**

- `500` — Schema resource unavailable
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Get a custom compliance standard

```http
GET /policies/api/v1/compliance/kyverno/custom-mappings/{standardKey}
```

Returns one custom standard including its policy-to-control mapping and the raw uploaded YAML.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standardKey` | `string` | Custom standard key |

**Response** `200` (`application/json`)

Custom standard detail

| Field | Type | Description |
|---|---|---|
| `standardKey` | `string` | Unique key of the custom standard |
| `displayName` | `string` | — |
| `policyCount` | `integer` | Number of mapped policies |
| `controlCount` | `integer` | Distinct controls mapped |
| `adminState` | `string` | — |
| `createdBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `updatedAt` | `string (date-time)` | — |
| `preferVpol` | `boolean` | — |
| `sources` | `object[]` | {id, title, ref, effective} |
| `policies` | `object` | Map of policy name to {clusterScoped, controls} |
| `nonAutomatable` | `object[]` | {id, family, reason} |
| `rawYaml` | `string` | The uploaded YAML |
| `contentSha256` | `string` | — |

**Errors**

- `404` — No such custom standard (code NOT_FOUND)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Update a custom compliance standard

```http
PUT /policies/api/v1/compliance/kyverno/custom-mappings/{standardKey}
```

Replaces an existing custom standard with a new YAML definition. The YAML's standardKey must match the path.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standardKey` | `string` | Custom standard key |

**Request body** (`text/yaml (also application/x-yaml, text/plain)`)

| Field | Type | Required | Description |
|---|---|---|---|
| `standardKey` | `string` | Yes | Lowercase letters, digits, hyphens; must not match a built-in standard |
| `displayName` | `string` | Yes | — |
| `preferVpol` | `boolean` | No | Informational |
| `sources` | `object[]` | No | {id, title, ref, effective} |
| `policies` | `object` | Yes | Map of Kyverno policy name to {clusterScoped, controls[]}; unknown policy names produce warnings, not errors |
| `non-automatable` | `object[]` | No | {id, family, reason} controls reported as not automatable |

**Response** `200` (`application/json`)

Custom standard detail plus optional warnings

| Field | Type | Description |
|---|---|---|
| `standardKey` | `string` | Unique key of the custom standard |
| `displayName` | `string` | — |
| `policyCount` | `integer` | Number of mapped policies |
| `controlCount` | `integer` | Distinct controls mapped |
| `adminState` | `string` | — |
| `createdBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `updatedAt` | `string (date-time)` | — |
| `preferVpol` | `boolean` | — |
| `sources` | `object[]` | {id, title, ref, effective} |
| `policies` | `object` | Map of policy name to {clusterScoped, controls} |
| `nonAutomatable` | `object[]` | {id, family, reason} |
| `rawYaml` | `string` | The uploaded YAML |
| `contentSha256` | `string` | — |
| `warnings` | `string[]` | Present only when there are warnings |

**Errors**

- `400` — YAML fails validation or standardKey mismatch (code VALIDATION_FAILED)
- `409` — No custom standard with that key exists to update (code CONFLICT)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Delete a custom compliance standard

```http
DELETE /policies/api/v1/compliance/kyverno/custom-mappings/{standardKey}
```

Deletes a custom standard and its standard-level enablement and target settings. Built-in standards cannot be deleted.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standardKey` | `string` | Custom standard key |

**Response** `204`

No content

**Errors**

- `404` — No such custom standard, or key is a built-in standard (code NOT_FOUND)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### List enabled compliance standards

```http
GET /policies/api/v1/compliance/kyverno/standards/enabled
```

Lists every standard enabled for the tenant with its effective targets. Targets that no longer exist in inventory are omitted.

**Roles:** `admin`, `security`

**Response** `200` (`application/json`)

Array of ComplianceStandardEnablementDto

| Field | Type | Description |
|---|---|---|
| `standard` | `string` | — |
| `standardDisplayName` | `string` | — |
| `custom` | `boolean` | — |
| `type` | `string` | built-in \| industry \| custom |
| `domain` | `string[]` | — |
| `enabled` | `boolean` | Can be false in PATCH responses |
| `applyToAllTargets` | `boolean` | Applies to every target of targetTypes |
| `targetTypes` | `string[]` | cluster \| repository |
| `historyEnabled` | `boolean` | Trend history tracking enabled |
| `targets` | `object[]` | Effective live targets {targetId, targetName, targetType} |

**Errors**

- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Rescan all enabled standards

```http
POST /policies/api/v1/compliance/kyverno/standards/rescan
```

Starts on-demand scans for every enabled standard and target pair, optionally limited to some standards. Pairs that cannot start (scan already running or capacity reached) are listed in skipped; the request still returns 200.

**Roles:** `admin`, `security`

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `standards` | `string[]` | No | Limit to these standards; omitted/empty = all enabled |

**Response** `200` (`application/json`)

ComplianceRescanResponseDto

| Field | Type | Description |
|---|---|---|
| `reportIds` | `object` | Map standard -&gt; targetId -&gt; new report ID |
| `skipped` | `object[]` | {standard, targetId, code: SCAN_IN_PROGRESS \| SCAN_CAPACITY_EXCEEDED} |
| `triggeredAt` | `string (date-time)` | — |

**Errors**

- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Get a standard's control catalog

```http
GET /policies/api/v1/compliance/kyverno/standards/{standard}/controls
```

Returns every control referenced by a standard with its name, description, whether it can be checked automatically, and the Kyverno policies mapped to it. Responses carry an ETag and Cache-Control (private, max-age=300); send If-None-Match to receive 304 when unchanged.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standard` | `string` | Standard key |

**Headers**

| Name | Required | Description |
|---|---|---|
| `If-None-Match` | No | ETag from a previous response; 304 if unchanged |

**Response** `200` (`application/json`)

`{standard, displayName, version, controls[]}`

| Field | Type | Description |
|---|---|---|
| `standard` | `string` | — |
| `displayName` | `string` | — |
| `version` | `string` | Content hash; also the ETag |
| `controls` | `object[]` | {id, name, description, automatable, family, reason, policies[]} in natural control-ID order |

**Errors**

- `304` — Not modified (If-None-Match matched)
- `404` — Unknown standard (code STANDARD_NOT_FOUND)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Enable a standard for targets

```http
PUT /policies/api/v1/compliance/kyverno/standards/{standard}/enablement
```

Enables a standard and replaces its target set, either an explicit list of clusters/repositories or all targets of the given types. Previously stored targets that no longer exist are dropped silently; a newly added unknown target is rejected.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standard` | `string` | Standard key |

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `applyToAllTargets` | `boolean` | No | Apply to every target of targetTypes |
| `targetTypes` | `string[]` | No | Required non-empty when applyToAllTargets; values cluster \| repository |
| `targets` | `object[]` | No | {targetId (uuid), targetType: cluster\|repository, targetName (ignored; server resolves)}; required unless applyToAllTargets |

**Response** `200` (`application/json`)

ComplianceStandardEnablementDto

| Field | Type | Description |
|---|---|---|
| `standard` | `string` | — |
| `standardDisplayName` | `string` | — |
| `custom` | `boolean` | — |
| `type` | `string` | built-in \| industry \| custom |
| `domain` | `string[]` | — |
| `enabled` | `boolean` | Can be false in PATCH responses |
| `applyToAllTargets` | `boolean` | Applies to every target of targetTypes |
| `targetTypes` | `string[]` | cluster \| repository |
| `historyEnabled` | `boolean` | Trend history tracking enabled |
| `targets` | `object[]` | Effective live targets {targetId, targetName, targetType} |

**Errors**

- `400` — REQUEST_BODY_REQUIRED, UNKNOWN_STANDARD, TARGETS_REQUIRED, TARGET_TYPES_REQUIRED, UNSUPPORTED_TARGET_TYPE, INVALID_TARGET_ID, or UNKNOWN_TARGET
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

**Example**

```json
{"applyToAllTargets":false,"targets":[{"targetId":"3f1c...","targetType":"cluster"}]}
```

### Update standard enablement flags

```http
PATCH /policies/api/v1/compliance/kyverno/standards/{standard}/enablement
```

Toggles enabled and/or historyEnabled for a standard without changing its target set. Omitted (null) fields are left unchanged. Re-enabling restores the previous targets.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standard` | `string` | Standard key |

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `enabled` | `boolean` | No | Enable/disable |
| `historyEnabled` | `boolean` | No | Enable/disable history tracking |

**Response** `200` (`application/json`)

ComplianceStandardEnablementDto

| Field | Type | Description |
|---|---|---|
| `standard` | `string` | — |
| `standardDisplayName` | `string` | — |
| `custom` | `boolean` | — |
| `type` | `string` | built-in \| industry \| custom |
| `domain` | `string[]` | — |
| `enabled` | `boolean` | Can be false in PATCH responses |
| `applyToAllTargets` | `boolean` | Applies to every target of targetTypes |
| `targetTypes` | `string[]` | cluster \| repository |
| `historyEnabled` | `boolean` | Trend history tracking enabled |
| `targets` | `object[]` | Effective live targets {targetId, targetName, targetType} |

**Errors**

- `400` — Body missing (REQUEST_BODY_REQUIRED)
- `404` — Standard has never been configured (code NOT_FOUND)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Disable a compliance standard

```http
DELETE /policies/api/v1/compliance/kyverno/standards/{standard}/enablement
```

Disables a standard for the tenant. Its target set is preserved, so re-enabling restores the same targets.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standard` | `string` | Standard key |

**Response** `204`

No content

**Errors**

- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Get a standard's policy-to-control mapping

```http
GET /policies/api/v1/compliance/kyverno/standards/{standard}/mappings
```

Returns the full policy-to-control mapping of a built-in or custom standard as JSON. With format=yaml, downloads it as a custom-standard YAML file ready to edit and upload; a built-in standard's key is rewritten to custom-<standard>.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standard` | `string` | Standard key |

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `format` | `string` | No | `json` | json or yaml |

**Response** `200` (`application/json (or text/yaml attachment when format=yaml)`)

ComplianceStandardMappingDto

| Field | Type | Description |
|---|---|---|
| `standard` | `string` | — |
| `displayName` | `string` | — |
| `type` | `string` | built-in \| industry \| custom |
| `custom` | `boolean` | — |
| `preferVpol` | `boolean` | — |
| `sources` | `object[]` | {id, title, ref, effective} |
| `policies` | `object[]` | {name, clusterScoped, path, controls[]} sorted by name |
| `nonAutomatable` | `object[]` | {id, family, reason} |

**Errors**

- `400` — format is not json or yaml (code INVALID_FORMAT)
- `404` — Unknown standard (code STANDARD_NOT_FOUND)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Rescan one compliance standard

```http
POST /policies/api/v1/compliance/kyverno/standards/{standard}/rescan
```

Starts on-demand scans for every target the standard is currently enabled for. Skipped pairs are listed; returns 200 with empty results if the standard is not enabled.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `standard` | `string` | Standard key |

**Response** `200` (`application/json`)

ComplianceRescanResponseDto

| Field | Type | Description |
|---|---|---|
| `reportIds` | `object` | Map standard -&gt; targetId -&gt; new report ID |
| `skipped` | `object[]` | {standard, targetId, code: SCAN_IN_PROGRESS \| SCAN_CAPACITY_EXCEEDED} |
| `triggeredAt` | `string (date-time)` | — |

**Errors**

- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles


---

## Compliance Reports


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

Read compliance reports per standard and target, drill into failing controls, export snapshots, view score history, and read namespace-level compliance.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [Get compliance score or control history](#get-compliance-score-or-control-history) | `GET` | `/compliance/kyverno/history` |
| [Get namespace compliance settings](#get-namespace-compliance-settings) | `GET` | `/compliance/kyverno/namespace-config` |
| [Update namespace compliance settings](#update-namespace-compliance-settings) | `PUT` | `/compliance/kyverno/namespace-config` |
| [List current namespace compliance reports](#list-current-namespace-compliance-reports) | `GET` | `/compliance/kyverno/namespace-reports` |
| [List compliance reports](#list-compliance-reports) | `GET` | `/compliance/kyverno/reports` |
| [Export a compliance snapshot](#export-a-compliance-snapshot) | `GET` | `/compliance/kyverno/reports/export` |
| [List targets with compliance reports](#list-targets-with-compliance-reports) | `GET` | `/compliance/kyverno/reports/targets` |
| [Get a compliance report](#get-a-compliance-report) | `GET` | `/compliance/kyverno/reports/{id}` |
| [Get a namespace compliance snapshot](#get-a-namespace-compliance-snapshot) | `GET` | `/compliance/kyverno/reports/{reportId}/namespaces/{namespace}` |
| [List failing resources for a report control](#list-failing-resources-for-a-report-control) | `GET` | `/compliance/kyverno/reports/{id}/controls/{controlId}/findings` |
| [List namespace findings for a control](#list-namespace-findings-for-a-control) | `GET` | `/compliance/kyverno/reports/{reportId}/namespaces/{namespace}/controls/{controlId}/findings` |

## Reference

### Get compliance score or control history

```http
GET /policies/api/v1/compliance/kyverno/history
```

Without controlId, returns the score trend for a target and standard; points exist only when results changed, so charts should carry the last value forward. With controlId, returns one point per completed scan for that control. Range defaults to the last 90 days.

**Roles:** `admin`, `platform`, `security`, `devops`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | Yes | — | Target ID (UUID) |
| `standard` | `string` | Yes | — | Standard key |
| `controlId` | `string` | No | — | Return control-level history for this control instead of score history |
| `from` | `string` | No | — | ISO-8601 start; default 90 days before 'to' |
| `to` | `string` | No | — | ISO-8601 end; default now |

**Response** `200` (`application/json`)

`Array, ascending by time: score points {time, score, passControls, failControls, totalControls, level} or, with controlId, {time, controlId, status, passCount, failCount}`

| Field | Type | Description |
|---|---|---|
| `time` | `string (date-time)` | — |
| `score` | `number` | 0-100 (score history) |
| `passControls` | `integer` | — |
| `failControls` | `integer` | — |
| `totalControls` | `integer` | — |
| `level` | `string` | green \| yellow \| red \| unknown |
| `controlId` | `string` | (control history) |
| `status` | `string` | pass \| fail \| not_evaluated \| not_automatable (control history) |
| `passCount` | `integer` | (control history) |
| `failCount` | `integer` | (control history) |

**Errors**

- `400` — targetId/standard missing, targetId not a UUID, or invalid from/to
- `400` — Control history range exceeds the maximum points (default 5000; code CONTROL_HISTORY_RANGE_TOO_LARGE) - narrow from/to
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Get namespace compliance settings

```http
GET /policies/api/v1/compliance/kyverno/namespace-config
```

Returns the namespace compliance settings for a cluster. If none have been saved, returns a disabled configuration at revision 0.

**Roles:** `admin`, `security`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | Yes | — | Cluster ID (UUID) |

**Response** `200` (`application/json`)

NamespaceComplianceConfigDto

| Field | Type | Description |
|---|---|---|
| `targetId` | `string (uuid)` | Cluster ID |
| `featureEnabled` | `boolean` | Whether this deployment supports namespace compliance |
| `enabled` | `boolean` | — |
| `mode` | `string` | all \| specific (nullable) |
| `namespaces` | `string[]` | Selected namespaces (specific mode) |
| `revision` | `integer` | Use as expectedRevision on update |
| `createdBy` | `string` | — |
| `updatedBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `updatedAt` | `string (date-time)` | — |

**Errors**

- `400` — targetId not a UUID (INVALID_TARGET_ID)
- `404` — Cluster not found or inaccessible
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Update namespace compliance settings

```http
PUT /policies/api/v1/compliance/kyverno/namespace-config
```

Saves which namespaces of a cluster get namespace-level compliance views: all namespaces or a specific list. Uses optimistic concurrency: expectedRevision must equal the current revision (0 to create).

**Roles:** `admin`, `security`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | Yes | — | Cluster ID (UUID) |

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `enabled` | `boolean` | Yes | — |
| `mode` | `string` | No | all \| specific; required when enabled or namespaces non-empty |
| `namespaces` | `string[]` | Yes | Valid Kubernetes namespace names; must be empty for all mode, non-empty for specific mode when enabled; use [] when none |
| `expectedRevision` | `integer` | Yes | Current revision from GET (&gt;=0) |

**Response** `200` (`application/json`)

NamespaceComplianceConfigDto

| Field | Type | Description |
|---|---|---|
| `targetId` | `string (uuid)` | Cluster ID |
| `featureEnabled` | `boolean` | Whether this deployment supports namespace compliance |
| `enabled` | `boolean` | — |
| `mode` | `string` | all \| specific (nullable) |
| `namespaces` | `string[]` | Selected namespaces (specific mode) |
| `revision` | `integer` | Use as expectedRevision on update |
| `createdBy` | `string` | — |
| `updatedBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `updatedAt` | `string (date-time)` | — |

**Errors**

- `400` — Invalid target, body, mode or namespace selection (e.g. ENABLED_REQUIRED, EXPECTED_REVISION_REQUIRED, NAMESPACES_REQUIRED, NAMESPACE_MODE_REQUIRED, NAMESPACES_NOT_ALLOWED, INVALID_NAMESPACE)
- `404` — Cluster not found or inaccessible
- `409` — Revision mismatch (CONFIG_REVISION_CONFLICT) or feature not available in this deployment (NAMESPACE_COMPLIANCE_UNAVAILABLE)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

**Example**

```json
{"enabled":true,"mode":"specific","namespaces":["payments","web"],"expectedRevision":0}
```

### List current namespace compliance reports

```http
GET /policies/api/v1/compliance/kyverno/namespace-reports
```

For a cluster and namespace, returns the latest completed report per standard projected onto that namespace, along with the current scope state. Never falls back to an older snapshot; if the namespace is not selected or data is pending, availability explains why.

**Roles:** `admin`, `platform`, `security`, `devops`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | Yes | — | Cluster ID (UUID) |
| `namespace` | `string` | Yes | — | Kubernetes namespace |
| `standard` | `string` | No | — | Filter by standard key |
| `page` | `integer` | No | `0` | 0-based page |
| `size` | `integer` | No | `50` | Page size, clamped to 1-100 |

**Response** `200` (`application/json`)

NamespaceComplianceReportsResponseDto

| Field | Type | Description |
|---|---|---|
| `targetId` | `string (uuid)` | — |
| `namespace` | `string` | — |
| `featureEnabled` | `boolean` | — |
| `enabled` | `boolean` | — |
| `mode` | `string` | all \| specific |
| `selectedNamespaces` | `string[]` | Empty unless caller has admin/platform/security role |
| `configRevision` | `integer` | — |
| `availability` | `string` | disabled \| not_selected \| pending \| available \| no_data \| projection_unavailable \| unsupported_scope |
| `reason` | `string` | — |
| `reports` | `object` | Page: {content: T[], totalElements, totalPages, number (0-based page), size} of {parentReportId, targetId, namespace, standard, scanTimestamp, projectionVersion, availability, reason, summary: {score (nullable 0-100), passCount, failCount, totalCount, notEvaluatedCount, notAutomatableCount, level}} |

**Errors**

- `400` — Invalid targetId or namespace
- `404` — Cluster or namespace not found or inaccessible
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### List compliance reports

```http
GET /policies/api/v1/compliance/kyverno/reports
```

Lists completed compliance reports. By default (latest=true) returns the most recent completed report for each target and standard combination as an unpaginated array; with latest=false returns all completed reports, newest first, paginated.

**Roles:** `admin`, `platform`, `security`, `devops`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | No | — | Filter by target ID (UUID) |
| `targetType` | `string` | No | — | Filter by target type, e.g. cluster |
| `standard` | `string` | No | — | Filter by standard key |
| `level` | `string` | No | — | Filter by level: green \| yellow \| red \| unknown |
| `latest` | `boolean` | No | `true` | Return only the latest report per target and standard |
| `page` | `integer` | No | `0` | 0-based page (latest=false only) |
| `size` | `integer` | No | `50` | Page size, clamped to 1-100 (latest=false only) |

**Response** `200` (`application/json`)

`latest=true: ComplianceReportDto[]; latest=false: Page: {content: T[], totalElements, totalPages, number (0-based page), size} of ComplianceReportDto`

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Report ID |
| `targetId` | `string (uuid)` | Scanned target (cluster/repository) ID |
| `targetType` | `string` | Target type, e.g. cluster, repository |
| `targetName` | `string` | Target display name |
| `standard` | `string` | Standard key, e.g. soc2 |
| `standardDisplayName` | `string` | Human-readable standard name |
| `scanTimestamp` | `string (date-time)` | When the scan ran |
| `status` | `string` | Report status (lists only return completed) |
| `triggerType` | `string` | scheduled \| on_demand \| published |
| `summary` | `object` | {score (0-100, null if nothing evaluated), passCount, failCount, totalCount, notEvaluatedCount, notAutomatableCount, level: green\|yellow\|red\|unknown} |

**Errors**

- `400` — targetId is not a valid UUID
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Export a compliance snapshot

```http
GET /policies/api/v1/compliance/kyverno/reports/export
```

Returns the most recent completed report for a target and standard at or before asOf, in the nctl snapshot JSON format (snake_case control fields) so it can be used with `nctl compliance diff`. Includes per-control findings and structured exception details.

**Roles:** `admin`, `platform`, `security`, `devops`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | Yes | — | Target ID (UUID) |
| `standard` | `string` | Yes | — | Standard key |
| `asOf` | `string` | No | — | ISO-8601 date-time with offset; defaults to now |

**Response** `200` (`application/json`)

nctl-compatible snapshot

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Snapshot ID in yyyyMMdd-HHmmss form |
| `standard` | `string` | — |
| `target` | `string` | Target name |
| `targetType` | `string` | — |
| `timestamp` | `string (date-time)` | Scan time |
| `exceptions` | `string[]` | Policy exceptions in effect |
| `exceptionDetails` | `object[]` | {name, namespace, policies[], managed, requestedBy, approvedBy, reason, state, startTime, expiryTime, history[]} |
| `controls` | `object[]` | {id, policy, policy_count, status, pass_count, fail_count, findings[]} |

**Errors**

- `400` — targetId/standard missing, targetId not a UUID, or invalid asOf
- `404` — No completed snapshot at or before asOf
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### List targets with compliance reports

```http
GET /policies/api/v1/compliance/kyverno/reports/targets
```

Returns each target that has at least one completed compliance report, with its most recent name, ordered by type and name. Useful for populating target pickers.

**Roles:** `admin`, `platform`, `security`, `devops`

**Response** `200` (`application/json`)

`Array of {targetId, targetName, targetType}`

| Field | Type | Description |
|---|---|---|
| `targetId` | `string (uuid)` | — |
| `targetName` | `string` | — |
| `targetType` | `string` | cluster \| repository \| cloud_account |

**Errors**

- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Get a compliance report

```http
GET /policies/api/v1/compliance/kyverno/reports/{id}
```

Returns one compliance report with its per-control results. For failed scans, errorMessage explains the failure.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Report ID (UUID) |

**Response** `200` (`application/json`)

`ComplianceReportDto with controls[]`

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Report ID |
| `targetId` | `string (uuid)` | Scanned target (cluster/repository) ID |
| `targetType` | `string` | Target type, e.g. cluster, repository |
| `targetName` | `string` | Target display name |
| `standard` | `string` | Standard key, e.g. soc2 |
| `standardDisplayName` | `string` | Human-readable standard name |
| `scanTimestamp` | `string (date-time)` | When the scan ran |
| `status` | `string` | Report status (lists only return completed) |
| `triggerType` | `string` | scheduled \| on_demand \| published |
| `summary` | `object` | {score (0-100, null if nothing evaluated), passCount, failCount, totalCount, notEvaluatedCount, notAutomatableCount, level: green\|yellow\|red\|unknown} |
| `errorMessage` | `string` | Set when status is failed |
| `controls` | `object[]` | {controlId, status: pass\|fail\|not_evaluated\|not_automatable, passCount, failCount, policiesTotal, policiesEvaluated, primaryPolicy, failingPolicies[], allPolicies[], notAutomatableReason} |

**Errors**

- `400` — id is not a valid UUID
- `404` — Report not found in this tenant (empty body)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Get a namespace compliance snapshot

```http
GET /policies/api/v1/compliance/kyverno/reports/{reportId}/namespaces/{namespace}
```

Returns the immutable namespace-level view of a cluster compliance report, including per-control results. If no namespace data exists for that report, availability and reason explain why.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `reportId` | `string` | Parent cluster report ID (UUID) |
| `namespace` | `string` | Kubernetes namespace |

**Response** `200` (`application/json`)

NamespaceComplianceReportDetailDto

| Field | Type | Description |
|---|---|---|
| `parentReportId` | `string (uuid)` | — |
| `targetId` | `string (uuid)` | — |
| `namespace` | `string` | — |
| `standard` | `string` | — |
| `scanTimestamp` | `string (date-time)` | — |
| `projectionVersion` | `integer` | — |
| `configRevision` | `integer` | — |
| `selectionMode` | `string` | all \| specific |
| `selectedNamespaces` | `string[]` | Empty unless caller has cluster-wide access |
| `availability` | `string` | disabled \| not_selected \| pending \| available \| no_data \| projection_unavailable \| unsupported_scope |
| `reason` | `string` | — |
| `summary` | `object` | {score (nullable 0-100), passCount, failCount, totalCount, notEvaluatedCount, notAutomatableCount, level} |
| `controls` | `object[]` | {controlId, status: pass\|fail\|not_evaluated\|not_automatable, passCount, failCount, policiesTotal, policiesEvaluated, primaryPolicy, failingPolicies[], allPolicies[], notAutomatableReason} |

**Errors**

- `400` — Invalid reportId (INVALID_REPORT_ID) or namespace (INVALID_NAMESPACE)
- `404` — Report, cluster or namespace not found or inaccessible
- `500` — Stored snapshot is invalid (NAMESPACE_SNAPSHOT_CORRUPT)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### List failing resources for a report control

```http
GET /policies/api/v1/compliance/kyverno/reports/{id}/controls/{controlId}/findings
```

Returns a paginated list of the failing or warning resources captured for one control of a report, ordered by namespace, kind and name. Returns an empty page if no evidence was captured for the control (including older reports).

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Report ID (UUID) |
| `controlId` | `string` | Control ID within the standard, e.g. CC6.1 |

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `page` | `integer` | No | `0` | 0-based page |
| `size` | `integer` | No | `50` | Page size, clamped to 1-100 |

**Response** `200` (`application/json`)

`Page: {content: T[], totalElements, totalPages, number (0-based page), size} of findings`

| Field | Type | Description |
|---|---|---|
| `policyName` | `string` | Kyverno policy |
| `ruleName` | `string` | Policy rule |
| `resourceKind` | `string` | Resource kind |
| `resourceName` | `string` | Resource name |
| `resourceNamespace` | `string` | Resource namespace |
| `message` | `string` | Policy result message |
| `result` | `string` | Policy result, e.g. fail or warn |

**Errors**

- `400` — id is not a valid UUID (code INVALID_ID)
- `404` — Report not found in this tenant (code REPORT_NOT_FOUND)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### List namespace findings for a control

```http
GET /policies/api/v1/compliance/kyverno/reports/{reportId}/namespaces/{namespace}/controls/{controlId}/findings
```

Returns a page of the retained failing-resource evidence for one control in a namespace snapshot. Evidence is sampled (up to 50 per control); totalElements counts retained findings and aggregateFailCount gives the true failure count.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `reportId` | `string` | Parent report ID (UUID) |
| `namespace` | `string` | Kubernetes namespace |
| `controlId` | `string` | Control ID |

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `page` | `integer` | No | `0` | 0-based page |
| `size` | `integer` | No | `50` | Page size, clamped to 1-100 |

**Response** `200` (`application/json`)

`{content: Finding[], totalElements, totalPages, number, size, sampleLimit, sampled, aggregateFailCount}`

| Field | Type | Description |
|---|---|---|
| `policyName` | `string` | Kyverno policy |
| `ruleName` | `string` | Policy rule |
| `resourceKind` | `string` | Resource kind |
| `resourceName` | `string` | Resource name |
| `resourceNamespace` | `string` | Resource namespace |
| `message` | `string` | Policy result message |
| `result` | `string` | Policy result, e.g. fail or warn |
| `sampleLimit` | `integer` | Max findings retained per control (50) |
| `sampled` | `boolean` | True when failures exceed retained evidence |
| `aggregateFailCount` | `integer` | Total failing evaluations |

**Errors**

- `400` — Invalid reportId, namespace, or blank controlId
- `404` — Report/namespace not found or inaccessible, or CONTROL_NOT_FOUND
- `409` — No available namespace snapshot for this report (NAMESPACE_SNAPSHOT_UNAVAILABLE)
- `500` — NAMESPACE_SNAPSHOT_CORRUPT
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles


---

## Compliance Scans


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

Configure scheduled compliance scans and run scans on demand.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [List compliance scan schedules](#list-compliance-scan-schedules) | `GET` | `/compliance/kyverno/config` |
| [Create or update a scan schedule](#create-or-update-a-scan-schedule) | `POST` | `/compliance/kyverno/config` |
| [Delete a scan schedule](#delete-a-scan-schedule) | `DELETE` | `/compliance/kyverno/config/{id}` |
| [Run an on-demand compliance scan](#run-an-on-demand-compliance-scan) | `POST` | `/compliance/kyverno/reports/trigger` |

## Reference

### List compliance scan schedules

```http
GET /policies/api/v1/compliance/kyverno/config
```

Returns the scheduled compliance scan configuration for each target in the tenant (one per target), ordered by ID. Optionally filter to one target.

**Roles:** `admin`, `security`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | No | — | Only return the configuration for this target (UUID) |

**Response** `200` (`application/json`)

Array of ComplianceConfigDto (not paginated)

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Configuration ID |
| `tenantId` | `string (uuid)` | Tenant ID |
| `targetId` | `string (uuid)` | Target ID |
| `targetType` | `string` | Target type, e.g. cluster |
| `targetName` | `string` | Target display name |
| `enabled` | `boolean` | Whether scheduled scans run |
| `cronExpression` | `string` | 5-field cron schedule |
| `standards` | `string[]` | Standards in scope; null = all standards |
| `lastCheckedAt` | `string (date-time)` | Last schedule check |
| `lastRunAt` | `string (date-time)` | Last scan run |
| `createdAt` | `string (date-time)` | — |
| `updatedAt` | `string (date-time)` | — |

**Errors**

- `400` — targetId is not a valid UUID
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Create or update a scan schedule

```http
POST /policies/api/v1/compliance/kyverno/config
```

Upserts the scan schedule for a target: there is one configuration per target, so posting again for the same targetId updates it in place. cronExpression defaults to '0 2 * * *' (daily 02:00); standards null means every supported standard.

**Roles:** `admin`, `security`

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `targetId` | `string (uuid)` | Yes | Target to scan |
| `targetType` | `string` | Yes | Target type, e.g. cluster |
| `targetName` | `string` | Yes | Target display name |
| `enabled` | `boolean` | No | Enable scheduled scans (defaults to false if omitted) |
| `cronExpression` | `string` | No | 5-field cron (minute hour dom month dow); default '0 2 * * *' |
| `standards` | `string[]` | No | Standard keys to scan; null/omitted = all standards |

**Response** `200` (`application/json`)

Empty body

**Errors**

- `400` — targetId not a UUID, missing required field, or cronExpression is not 5 fields
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

**Example**

```json
{"targetId":"3f1c...","targetType":"cluster","targetName":"prod-eks","enabled":true,"cronExpression":"0 2 * * *","standards":["soc2","cis-eks"]}
```

### Delete a scan schedule

```http
DELETE /policies/api/v1/compliance/kyverno/config/{id}
```

Removes a target's scan schedule configuration.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Configuration ID (UUID) from the list response, not the target ID |

**Response** `204`

No content

**Errors**

- `400` — id is not a valid UUID
- `404` — No such configuration in this tenant (empty body)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Run an on-demand compliance scan

```http
POST /policies/api/v1/compliance/kyverno/reports/trigger
```

Starts an immediate compliance scan of one target, one report per standard. Runs asynchronously: returns 202 with the new report IDs, which can be polled via GET /compliance/kyverno/reports/{id}. On-demand scans always run even if nothing changed since the last scan.

**Roles:** `admin`, `security`

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `targetId` | `string (uuid)` | Yes | Target to scan |
| `targetType` | `string` | Yes | Target type, e.g. cluster |
| `targetName` | `string` | Yes | Target display name |
| `standards` | `string[]` | No | Standards to scan; null/empty = all standards in the tenant catalog |

**Response** `202` (`application/json`)

ComplianceTriggerResponseDto

| Field | Type | Description |
|---|---|---|
| `reportIds` | `object` | Map of standard key to new report ID, e.g. {"soc2":"uuid"} |
| `message` | `string` | Human-readable status |
| `triggeredAt` | `string (date-time)` | Trigger time |

**Errors**

- `400` — targetId not a UUID (code INVALID_TARGET_ID) or required field missing
- `409` — A scan is already in progress for a requested target/standard (code SCAN_IN_PROGRESS; body includes existing reportId)
- `429` — Scan capacity exceeded (code SCAN_CAPACITY_EXCEEDED)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

**Example**

```json
{"targetId":"3f1c...","targetType":"cluster","targetName":"prod-eks","standards":["soc2"]}
```


---

## Compliance Audit Reports


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

Generate compliance audit reports across standards and targets, then list, download, and delete the generated report artifacts.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [List compliance report artifacts](#list-compliance-report-artifacts) | `GET` | `/compliance/reports/artifacts` |
| [Generate a compliance audit report](#generate-a-compliance-audit-report) | `POST` | `/compliance/reports/generate` |
| [Get a compliance report artifact](#get-a-compliance-report-artifact) | `GET` | `/compliance/reports/artifacts/{id}` |
| [Delete a compliance report artifact](#delete-a-compliance-report-artifact) | `DELETE` | `/compliance/reports/artifacts/{id}` |
| [Download a compliance report artifact](#download-a-compliance-report-artifact) | `GET` | `/compliance/reports/artifacts/{id}/download` |
| [Generate a scheduled report now](#generate-a-scheduled-report-now) | `POST` | `/compliance/reports/schedules/{scheduleId}/generate-now` |

## Reference

### List compliance report artifacts

```http
GET /policies/api/v1/compliance/reports/artifacts
```

Lists stored compliance report artifacts, newest first. Filters match both single-scope and composed audit reports.

**Roles:** `admin`, `platform`, `security`, `devops`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | No | — | Filter by target UUID |
| `standard` | `string` | No | — | Filter by standard key |
| `scheduleId` | `string` | No | — | Filter by originating schedule |
| `source` | `string` | No | — | Filter by source: policy_hub \| compliance_standard \| audit |
| `limit` | `integer` | No | `100` | Page size; &lt;=0 uses default, max 500 |
| `offset` | `integer` | No | `0` | Items to skip (&gt;=0) |

**Response** `200` (`application/json`)

Array of artifact summaries (no total count)

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Artifact ID |
| `scheduleRef` | `string` | Schedule that produced it, if any |
| `source` | `string` | policy_hub \| compliance_standard \| audit |
| `targetId` | `string (uuid)` | Single-scope artifacts only |
| `targetType` | `string` | — |
| `targetName` | `string` | — |
| `standard` | `string` | Single-scope artifacts only |
| `generatedAt` | `string (date-time)` | — |
| `triggerType` | `string` | scheduled \| on_demand |
| `score` | `number` | — |
| `level` | `string` | green \| yellow \| red \| unknown |
| `passControls` | `integer` | — |
| `failControls` | `integer` | — |
| `totalControls` | `integer` | — |
| `standards` | `string[]` | Composed audit reports |
| `targetIds` | `string[]` | Composed audit reports |
| `periodFrom` | `string (date-time)` | Composed audit reports |
| `periodTo` | `string (date-time)` | Composed audit reports |

**Errors**

- `400` — INVALID_OFFSET or INVALID_FILTER (bad targetId)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Generate a compliance audit report

```http
POST /policies/api/v1/compliance/reports/generate
```

Generates a composed audit report covering several standards and targets over a time period and stores it as an artifact. The period defaults to the last 30 days.

**Roles:** `admin`, `security`

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `standards` | `string[]` | Yes | Standard keys (must be known) |
| `targetIds` | `string[]` | Yes | Target UUIDs |
| `from` | `string (date-time)` | No | Period start; must be given together with to |
| `to` | `string (date-time)` | No | Period end; after from |
| `periodDays` | `integer` | No | Rolling window when from/to absent; default 30, max 10000 |

**Response** `200` (`application/json`)

`{artifactId, generatedAt, score, level, sectionsWithData, sectionsWithoutData}`

| Field | Type | Description |
|---|---|---|
| `artifactId` | `string (uuid)` | — |
| `generatedAt` | `string (date-time)` | — |
| `score` | `number` | — |
| `level` | `string` | — |
| `sectionsWithData` | `integer` | Standard x target sections with scan data |
| `sectionsWithoutData` | `integer` | Sections with no data |

**Errors**

- `400` — Invalid options, e.g. unknown standard, bad target UUID, from/to mismatch, scope too large (INVALID_REPORT_CONFIG)
- `500` — GENERATE_FAILED
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

**Example**

```json
{"standards":["soc2","cis-eks"],"targetIds":["3f1c..."],"from":"2026-09-01T00:00:00Z","to":"2026-10-01T00:00:00Z"}
```

### Get a compliance report artifact

```http
GET /policies/api/v1/compliance/reports/artifacts/{id}
```

Returns an artifact's metadata plus the full report content: an nctl-format snapshot for single-scope artifacts, or the composed audit report for multi-standard reports.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Artifact ID (UUID) |

**Response** `200` (`application/json`)

Artifact summary fields + contentSha256 + report

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Artifact ID |
| `scheduleRef` | `string` | Schedule that produced it, if any |
| `source` | `string` | policy_hub \| compliance_standard \| audit |
| `targetId` | `string (uuid)` | Single-scope artifacts only |
| `targetType` | `string` | — |
| `targetName` | `string` | — |
| `standard` | `string` | Single-scope artifacts only |
| `generatedAt` | `string (date-time)` | — |
| `triggerType` | `string` | scheduled \| on_demand |
| `score` | `number` | — |
| `level` | `string` | green \| yellow \| red \| unknown |
| `passControls` | `integer` | — |
| `failControls` | `integer` | — |
| `totalControls` | `integer` | — |
| `standards` | `string[]` | Composed audit reports |
| `targetIds` | `string[]` | Composed audit reports |
| `periodFrom` | `string (date-time)` | Composed audit reports |
| `periodTo` | `string (date-time)` | Composed audit reports |
| `contentSha256` | `string` | Hash of stored report content |
| `report` | `object` | Snapshot or composed audit report |

**Errors**

- `404` — Artifact not found or id malformed (ARTIFACT_NOT_FOUND)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Delete a compliance report artifact

```http
DELETE /policies/api/v1/compliance/reports/artifacts/{id}
```

Permanently deletes a stored report artifact.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Artifact ID (UUID) |

**Response** `204`

No content

**Errors**

- `404` — Artifact not found or id malformed (ARTIFACT_NOT_FOUND)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Download a compliance report artifact

```http
GET /policies/api/v1/compliance/reports/artifacts/{id}/download
```

Downloads an artifact as a JSON, CSV, or PDF attachment. PDF is available only for composed audit reports and is cached after the first render.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Artifact ID (UUID) |

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `format` | `string` | No | `json` | json \| csv \| pdf |

**Response** `200` (`application/json | text/csv | application/pdf`)

`File attachment (compliance-report-<scope>-<id>.<ext>)`

**Errors**

- `400` — Unsupported format (UNSUPPORTED_FORMAT)
- `404` — Artifact not found (ARTIFACT_NOT_FOUND) or PDF requested for a non-audit artifact (PDF_NOT_AVAILABLE)
- `500` — PDF rendering failed (PDF_RENDER_FAILED)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Generate a scheduled report now

```http
POST /policies/api/v1/compliance/reports/schedules/{scheduleId}/generate-now
```

Immediately generates a report artifact using a compliance report schedule's saved configuration (compliance history, compliance standard history, or audit report types). The run is recorded as on-demand but attributed to the schedule.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `scheduleId` | `string` | Report schedule ID |

**Response** `200` (`application/json`)

`{artifactId, generatedAt, score}`

| Field | Type | Description |
|---|---|---|
| `artifactId` | `string (uuid)` | — |
| `generatedAt` | `string (date-time)` | — |
| `score` | `number` | — |

**Errors**

- `400` — Schedule is not a compliance report schedule (NOT_A_COMPLIANCE_HISTORY_SCHEDULE) or its config is invalid (INVALID_REPORT_CONFIG)
- `404` — Schedule not found (SCHEDULE_NOT_FOUND)
- `409` — Report already exists for the period (DUPLICATE_PERIOD)
- `500` — GENERATE_FAILED
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles


---

## Compliance Publishing


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

Publish compliance snapshots and evidence packages to Nirmata Control Hub. `nctl` uses these endpoints when publishing compliance results. You can also call them directly from your own tooling.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [List evidence packages](#list-evidence-packages) | `GET` | `/compliance/evidence-packages` |
| [Declare an evidence package](#declare-an-evidence-package) | `POST` | `/compliance/evidence-packages` |
| [Publish a compliance snapshot](#publish-a-compliance-snapshot) | `POST` | `/compliance/snapshots` |
| [Get an evidence package](#get-an-evidence-package) | `GET` | `/compliance/evidence-packages/{id}` |
| [Download an evidence package](#download-an-evidence-package) | `GET` | `/compliance/evidence-packages/{id}/download` |
| [Upload an evidence package part](#upload-an-evidence-package-part) | `PUT` | `/compliance/evidence-packages/{id}/parts/{index}` |

## Reference

### List evidence packages

```http
GET /policies/api/v1/compliance/evidence-packages
```

Lists the tenant's evidence packages, newest collection first, optionally filtered by target and standard.

**Roles:** `admin`, `platform`, `security`, `devops`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `targetId` | `string` | No | — | Filter by cluster ID (UUID) |
| `standard` | `string` | No | — | Filter by standard key |
| `limit` | `integer` | No | `50` | Page size (1-500) |
| `offset` | `integer` | No | `0` | Items to skip (&gt;=0) |

**Response** `200` (`application/json`)

Array of EvidencePackageDto (no total count)

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Package ID |
| `publishId` | `string (uuid)` | Idempotency key |
| `targetId` | `string (uuid)` | Cluster ID |
| `targetName` | `string` | Cluster name |
| `standard` | `string` | — |
| `reportId` | `string (uuid)` | Linked snapshot report, if any |
| `publisher` | `string` | — |
| `publisherVersion` | `string` | — |
| `collectedAt` | `string (date-time)` | — |
| `contentType` | `string` | Archive media type |
| `sha256` | `string` | Hex SHA-256 of the archive |
| `sizeBytes` | `integer` | — |
| `partSizeBytes` | `integer` | Required size of every part except the last |
| `partCount` | `integer` | — |
| `receivedParts` | `integer[]` | Part indexes already stored (for resuming) |
| `status` | `string` | uploading \| complete \| failed \| deleting |
| `errorMessage` | `string` | Set when failed |
| `metadata` | `object` | Producer metadata |
| `createdBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `completedAt` | `string (date-time)` | — |
| `duplicate` | `boolean` | Only on the declare response |

**Errors**

- `400` — INVALID_PAGINATION or INVALID_ID (bad targetId)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Declare an evidence package

```http
POST /policies/api/v1/compliance/evidence-packages
```

Declares an evidence archive (size, SHA-256, metadata) before uploading its bytes in parts. The response gives partSizeBytes/partCount. Idempotent on publishId: re-declaring returns the existing package with its receivedParts (200, duplicate=true) so an interrupted upload can resume.

**Roles:** `admin`, `security`

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `publishId` | `string (uuid)` | Yes | Idempotency key |
| `schemaVersion` | `string` | Yes | Envelope version ("v1") |
| `publisher` | `object` | Yes | {name, version} |
| `cluster` | `object` | Yes | {id, kubeSystemUid, name}; at least one of id/kubeSystemUid |
| `standard` | `string` | Yes | Standard key |
| `collectedAt` | `string (date-time)` | Yes | When evidence was collected |
| `snapshotPublishId` | `string (uuid)` | No | publishId of a snapshot already published for the same cluster/standard |
| `contentType` | `string` | Yes | application/gzip \| application/x-gzip \| application/x-tar \| application/zip |
| `sizeBytes` | `integer` | Yes | Archive size (&gt;0, subject to a server max) |
| `sha256` | `string` | Yes | 64 hex chars |
| `metadata` | `object` | No | JSON object of producer metadata (size-capped) |

**Response** `201` (`application/json`)

EvidencePackageDto (200 when duplicate)

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Package ID |
| `publishId` | `string (uuid)` | Idempotency key |
| `targetId` | `string (uuid)` | Cluster ID |
| `targetName` | `string` | Cluster name |
| `standard` | `string` | — |
| `reportId` | `string (uuid)` | Linked snapshot report, if any |
| `publisher` | `string` | — |
| `publisherVersion` | `string` | — |
| `collectedAt` | `string (date-time)` | — |
| `contentType` | `string` | Archive media type |
| `sha256` | `string` | Hex SHA-256 of the archive |
| `sizeBytes` | `integer` | — |
| `partSizeBytes` | `integer` | Required size of every part except the last |
| `partCount` | `integer` | — |
| `receivedParts` | `integer[]` | Part indexes already stored (for resuming) |
| `status` | `string` | uploading \| complete \| failed \| deleting |
| `errorMessage` | `string` | Set when failed |
| `metadata` | `object` | Producer metadata |
| `createdBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `completedAt` | `string (date-time)` | — |
| `duplicate` | `boolean` | Only on the declare response |

**Errors**

- `400` — Validation failure (e.g. INVALID_PUBLISH_ID, UNSUPPORTED_CONTENT_TYPE, INVALID_SHA256, INVALID_SIZE, INVALID_METADATA, INVALID_SNAPSHOT_PUBLISH_ID, cluster errors)
- `409` — PUBLISH_ID_CONFLICT, PACKAGE_DELETED (expired), or AMBIGUOUS_CLUSTER
- `413` — EVIDENCE_PACKAGE_TOO_LARGE
- `422` — CLUSTER_NOT_FOUND / CLUSTER_MISMATCH, SNAPSHOT_NOT_FOUND, or SNAPSHOT_MISMATCH
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Publish a compliance snapshot

```http
POST /policies/api/v1/compliance/snapshots
```

Stores a compliance snapshot computed outside Nirmata (for example by `nctl scan compliance --publish`) as a completed compliance report for the identified cluster. Idempotent on publishId: returns 201 when created and 200 with duplicate=true (describing the original report) when that publishId was already recorded. Controls unknown to the tenant's catalog are dropped and listed in ignoredControls.

**Roles:** `admin`, `security`

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `publishId` | `string (uuid)` | Yes | Producer-generated idempotency key |
| `schemaVersion` | `string` | Yes | Envelope version; only "v1" accepted |
| `publisher` | `object` | Yes | {name: lowercase tool id e.g. "nctl" (required), version (&lt;=128 chars)} |
| `cluster` | `object` | Yes | {id: Nirmata cluster UUID, kubeSystemUid: kube-system namespace UID, name (informational)}; at least one of id/kubeSystemUid required, must agree if both given |
| `snapshot` | `object` | Yes | nctl state.Snapshot: {id, standard (required, must be a known standard), cluster, context, timestamp (required, not in the future), controls: [{id, policy, policy_count, status: pass\|fail\|not-evaluated, pass_count, fail_count}], exceptions: ["ns/name"]} |

**Response** `201` (`application/json`)

SnapshotPublishResponseDto (200 when duplicate)

| Field | Type | Description |
|---|---|---|
| `reportId` | `string (uuid)` | Created (or original) report ID |
| `publishId` | `string` | — |
| `targetId` | `string (uuid)` | Resolved cluster ID |
| `targetName` | `string` | Nirmata cluster name |
| `standard` | `string` | — |
| `scanTimestamp` | `string (date-time)` | — |
| `score` | `number` | 0-100 |
| `level` | `string` | green \| yellow \| red \| unknown |
| `duplicate` | `boolean` | True if publishId was already recorded |
| `ignoredControls` | `string[]` | Control IDs dropped as unknown (empty on duplicate) |

**Errors**

- `400` — Validation failure, e.g. BODY_REQUIRED, PUBLISH_ID_REQUIRED, INVALID_PUBLISH_ID, UNSUPPORTED_SCHEMA_VERSION, INVALID_PUBLISHER, CLUSTER_REQUIRED, INVALID_CLUSTER_ID, SNAPSHOT_REQUIRED, STANDARD_REQUIRED, UNKNOWN_STANDARD, TIMESTAMP_REQUIRED, FUTURE_TIMESTAMP, unsupported control status
- `409` — PUBLISH_ID_CONFLICT (publishId reused for different content) or AMBIGUOUS_CLUSTER (several clusters share the kube-system UID)
- `422` — CLUSTER_NOT_FOUND or CLUSTER_MISMATCH (id and kubeSystemUid refer to different clusters)
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Get an evidence package

```http
GET /policies/api/v1/compliance/evidence-packages/{id}
```

Returns one evidence package's metadata and upload status.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Package ID (UUID) |

**Response** `200` (`application/json`)

EvidencePackageDto

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Package ID |
| `publishId` | `string (uuid)` | Idempotency key |
| `targetId` | `string (uuid)` | Cluster ID |
| `targetName` | `string` | Cluster name |
| `standard` | `string` | — |
| `reportId` | `string (uuid)` | Linked snapshot report, if any |
| `publisher` | `string` | — |
| `publisherVersion` | `string` | — |
| `collectedAt` | `string (date-time)` | — |
| `contentType` | `string` | Archive media type |
| `sha256` | `string` | Hex SHA-256 of the archive |
| `sizeBytes` | `integer` | — |
| `partSizeBytes` | `integer` | Required size of every part except the last |
| `partCount` | `integer` | — |
| `receivedParts` | `integer[]` | Part indexes already stored (for resuming) |
| `status` | `string` | uploading \| complete \| failed \| deleting |
| `errorMessage` | `string` | Set when failed |
| `metadata` | `object` | Producer metadata |
| `createdBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `completedAt` | `string (date-time)` | — |
| `duplicate` | `boolean` | Only on the declare response |

**Errors**

- `400` — INVALID_ID
- `404` — PACKAGE_NOT_FOUND
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Download an evidence package

```http
GET /policies/api/v1/compliance/evidence-packages/{id}/download
```

Streams the complete evidence archive as an attachment (evidence-<standard>-<id>.tar.gz|tar|zip), with Content-Length and an X-Content-SHA256 header for integrity checking. Only complete packages can be downloaded.

**Roles:** `admin`, `platform`, `security`, `devops`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Package ID (UUID) |

**Response** `200` (`archive media type of the package (e.g. application/gzip)`)

Binary archive; headers Content-Disposition, Content-Length, X-Content-SHA256

**Errors**

- `400` — INVALID_ID
- `404` — PACKAGE_NOT_FOUND
- `409` — PACKAGE_NOT_COMPLETE, PACKAGE_FAILED or PACKAGE_DELETED
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

### Upload an evidence package part

```http
PUT /policies/api/v1/compliance/evidence-packages/{id}/parts/{index}
```

Uploads one part of a declared package as raw bytes. Every part except the last must be exactly partSizeBytes. When the last missing part arrives the archive is verified against its SHA-256 and the package becomes complete (or failed). Re-uploading a part is safe.

**Roles:** `admin`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Package ID (UUID) |
| `index` | `integer` | Zero-based part index (0 to partCount-1) |

**Request body** (`application/octet-stream`)

**Response** `200` (`application/json`)

EvidencePackageDto

| Field | Type | Description |
|---|---|---|
| `id` | `string (uuid)` | Package ID |
| `publishId` | `string (uuid)` | Idempotency key |
| `targetId` | `string (uuid)` | Cluster ID |
| `targetName` | `string` | Cluster name |
| `standard` | `string` | — |
| `reportId` | `string (uuid)` | Linked snapshot report, if any |
| `publisher` | `string` | — |
| `publisherVersion` | `string` | — |
| `collectedAt` | `string (date-time)` | — |
| `contentType` | `string` | Archive media type |
| `sha256` | `string` | Hex SHA-256 of the archive |
| `sizeBytes` | `integer` | — |
| `partSizeBytes` | `integer` | Required size of every part except the last |
| `partCount` | `integer` | — |
| `receivedParts` | `integer[]` | Part indexes already stored (for resuming) |
| `status` | `string` | uploading \| complete \| failed \| deleting |
| `errorMessage` | `string` | Set when failed |
| `metadata` | `object` | Producer metadata |
| `createdBy` | `string` | — |
| `createdAt` | `string (date-time)` | — |
| `completedAt` | `string (date-time)` | — |
| `duplicate` | `boolean` | Only on the declare response |

**Errors**

- `400` — INVALID_ID, INVALID_PART_INDEX, or INVALID_PART_SIZE
- `404` — PACKAGE_NOT_FOUND
- `409` — PACKAGE_FAILED or PACKAGE_DELETED
- `401` — Not authenticated
- `403` — Caller's role is not in the allowed roles

**Example**

```bash
curl -X PUT -H 'Content-Type: application/octet-stream' --data-binary @part0.bin .../compliance/evidence-packages/{id}/parts/0
```


---

## Compliance Catalog


<!-- Generated by scripts/gen-policies-api-v1/gen.py. Edit inventory.json, not this file. -->

Read the compliance standards and controls catalog, including control results and summaries.

All paths are relative to `/policies/api/v1`. See [Policies API v1](../) for authentication and conventions.

## Endpoints

| Operation | Method | Path |
|---|---|---|
| [List compliance controls](#list-compliance-controls) | `GET` | `/compliance-controls` |
| [List compliance standards](#list-compliance-standards) | `GET` | `/compliance-standards` |
| [Count compliance controls](#count-compliance-controls) | `GET` | `/compliance-controls/count` |
| [Get compliance controls summary](#get-compliance-controls-summary) | `GET` | `/compliance-controls/summary` |
| [Get a compliance control](#get-a-compliance-control) | `GET` | `/compliance-controls/{id}` |
| [List built-in compliance standards](#list-built-in-compliance-standards) | `GET` | `/compliance-standards/built-in` |
| [Count compliance standards](#count-compliance-standards) | `GET` | `/compliance-standards/count` |
| [Get compliance standards summary](#get-compliance-standards-summary) | `GET` | `/compliance-standards/summary` |
| [Get a compliance standard](#get-a-compliance-standard) | `GET` | `/compliance-standards/{id}` |
| [List compliance controls by admin state](#list-compliance-controls-by-admin-state) | `GET` | `/compliance-controls/by-admin-state/{adminState}` |
| [List compliance controls by cloud provider](#list-compliance-controls-by-cloud-provider) | `GET` | `/compliance-controls/by-cloud-provider/{cloudProvider}` |
| [List compliance controls by control ID](#list-compliance-controls-by-control-id) | `GET` | `/compliance-controls/by-control-id/{controlId}` |
| [List compliance controls by status](#list-compliance-controls-by-status) | `GET` | `/compliance-controls/by-status/{status}` |
| [List compliance controls by subject area](#list-compliance-controls-by-subject-area) | `GET` | `/compliance-controls/by-subject-area/{subjectArea}` |
| [Get results for a compliance control](#get-results-for-a-compliance-control) | `GET` | `/compliance-controls/{id}/results` |
| [List compliance standards by category](#list-compliance-standards-by-category) | `GET` | `/compliance-standards/by-category/{category}` |
| [List compliance standards by type](#list-compliance-standards-by-type) | `GET` | `/compliance-standards/by-type/{type}` |
| [Refresh compliance results for a standard](#refresh-compliance-results-for-a-standard) | `POST` | `/compliance-standards/{id}/refresh` |

## Reference

### List compliance controls

```http
GET /policies/api/v1/compliance-controls
```

Returns all compliance controls for the tenant, paginated in memory.

**Roles:** `admin`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `limit` | `integer` | No | `50` | Maximum number of items to return |
| `offset` | `integer` | No | `0` | Number of items to skip |

**Response** `200` (`application/json`)

`Paginated: {items: ComplianceControlDto[], total, limit, offset}`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Control ID (UUID) |
| `name` | `string` | Control name |
| `controlId` | `string` | Standard control identifier, e.g. 1.1.1 |
| `subControlId` | `string` | — |
| `description` | `string` | — |
| `subjectArea` | `string` | — |
| `cloudProviders` | `string[]` | — |
| `adminState` | `string` | enabled \| disabled |
| `status` | `string` | Control state |
| `statusBySourceType` | `object` | Status per source type |
| `policies` | `string[]` | Mapped policy names |
| `properties` | `object` | — |
| `isManual` | `boolean` | Requires manual attestation |
| `manualComplianceDetails` | `object` | — |
| `validSourceTypes` | `string[]` | — |
| `fullControlId` | `string` | controlId plus subControlId (computed) |
| `displayName` | `string` | computed |
| `policyCount` | `integer` | computed |

**Errors**

- `500` — Retrieval failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List compliance standards

```http
GET /policies/api/v1/compliance-standards
```

Returns all compliance standards for the tenant, paginated in memory. Controls are not included.

**Roles:** `admin`

**Query parameters**

| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| `limit` | `integer` | No | `50` | Maximum number of items to return |
| `offset` | `integer` | No | `0` | Number of items to skip |

**Response** `200` (`application/json`)

`Paginated: {items: ComplianceStandardDto[], total, limit, offset}`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Standard ID (UUID) |
| `name` | `string` | — |
| `version` | `string` | — |
| `description` | `string` | — |
| `type` | `string` | nirmataManaged (built-in) \| userManaged (custom) |
| `category` | `string` | Standard category, e.g. cis |
| `adminState` | `string` | enabled \| disabled |
| `score` | `integer` | Compliance score |
| `lastUpdateTime` | `integer (epoch ms)` | — |
| `k8sMinVersion` | `string` | Minimum supported Kubernetes version |
| `k8sMaxVersion` | `string` | Maximum supported Kubernetes version |
| `grade` | `string` | A-F derived from score, N/A if no score (computed) |
| `displayName` | `string` | computed |
| `controlCount` | `integer` | computed; controls are not populated by this API |

**Errors**

- `500` — Retrieval failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### Count compliance controls

```http
GET /policies/api/v1/compliance-controls/count
```

Returns the number of compliance controls for the tenant.

**Roles:** `admin`

**Response** `200` (`application/json`)

`{count}`

| Field | Type | Description |
|---|---|---|
| `count` | `integer` | — |

### Get compliance controls summary

```http
GET /policies/api/v1/compliance-controls/summary
```

Returns counts of total, active/inactive, and enabled/disabled compliance controls.

**Roles:** `admin`

**Response** `200` (`application/json`)

`{total, active, inactive, enabled, disabled}`

| Field | Type | Description |
|---|---|---|
| `total` | `integer` | — |
| `active` | `integer` | — |
| `inactive` | `integer` | — |
| `enabled` | `integer` | — |
| `disabled` | `integer` | — |

### Get a compliance control

```http
GET /policies/api/v1/compliance-controls/{id}
```

Returns a single compliance control by ID.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Control UUID |

**Response** `200` (`application/json`)

ComplianceControlDto

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Control ID (UUID) |
| `name` | `string` | Control name |
| `controlId` | `string` | Standard control identifier, e.g. 1.1.1 |
| `subControlId` | `string` | — |
| `description` | `string` | — |
| `subjectArea` | `string` | — |
| `cloudProviders` | `string[]` | — |
| `adminState` | `string` | enabled \| disabled |
| `status` | `string` | Control state |
| `statusBySourceType` | `object` | Status per source type |
| `policies` | `string[]` | Mapped policy names |
| `properties` | `object` | — |
| `isManual` | `boolean` | Requires manual attestation |
| `manualComplianceDetails` | `object` | — |
| `validSourceTypes` | `string[]` | — |
| `fullControlId` | `string` | controlId plus subControlId (computed) |
| `displayName` | `string` | computed |
| `policyCount` | `integer` | computed |

**Errors**

- `404` — Control not found
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List built-in compliance standards

```http
GET /policies/api/v1/compliance-standards/built-in
```

Returns Nirmata-managed (built-in) compliance standards for the tenant. Not paginated.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Response** `200` (`application/json`)

`ComplianceStandardDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Standard ID (UUID) |
| `name` | `string` | — |
| `version` | `string` | — |
| `description` | `string` | — |
| `type` | `string` | nirmataManaged (built-in) \| userManaged (custom) |
| `category` | `string` | Standard category, e.g. cis |
| `adminState` | `string` | enabled \| disabled |
| `score` | `integer` | Compliance score |
| `lastUpdateTime` | `integer (epoch ms)` | — |
| `k8sMinVersion` | `string` | Minimum supported Kubernetes version |
| `k8sMaxVersion` | `string` | Maximum supported Kubernetes version |
| `grade` | `string` | A-F derived from score, N/A if no score (computed) |
| `displayName` | `string` | computed |
| `controlCount` | `integer` | computed; controls are not populated by this API |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### Count compliance standards

```http
GET /policies/api/v1/compliance-standards/count
```

Returns the number of compliance standards for the tenant.

**Roles:** `admin`

**Response** `200` (`application/json`)

`{count}`

| Field | Type | Description |
|---|---|---|
| `count` | `integer` | — |

### Get compliance standards summary

```http
GET /policies/api/v1/compliance-standards/summary
```

Returns counts of total, built-in, custom, active (adminState enabled), and inactive compliance standards.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Response** `200` (`application/json`)

`{total, builtIn, custom, active, inactive}`

| Field | Type | Description |
|---|---|---|
| `total` | `integer` | — |
| `builtIn` | `integer` | — |
| `custom` | `integer` | — |
| `active` | `integer` | — |
| `inactive` | `integer` | — |

### Get a compliance standard

```http
GET /policies/api/v1/compliance-standards/{id}
```

Returns a single compliance standard by ID.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Standard UUID |

**Response** `200` (`application/json`)

ComplianceStandardDto

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Standard ID (UUID) |
| `name` | `string` | — |
| `version` | `string` | — |
| `description` | `string` | — |
| `type` | `string` | nirmataManaged (built-in) \| userManaged (custom) |
| `category` | `string` | Standard category, e.g. cis |
| `adminState` | `string` | enabled \| disabled |
| `score` | `integer` | Compliance score |
| `lastUpdateTime` | `integer (epoch ms)` | — |
| `k8sMinVersion` | `string` | Minimum supported Kubernetes version |
| `k8sMaxVersion` | `string` | Maximum supported Kubernetes version |
| `grade` | `string` | A-F derived from score, N/A if no score (computed) |
| `displayName` | `string` | computed |
| `controlCount` | `integer` | computed; controls are not populated by this API |

**Errors**

- `404` — Standard not found
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List compliance controls by admin state

```http
GET /policies/api/v1/compliance-controls/by-admin-state/{adminState}
```

Returns all compliance controls whose admin state matches the given value. Not paginated.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `adminState` | `string` | enabled \| disabled |

**Response** `200` (`application/json`)

`ComplianceControlDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Control ID (UUID) |
| `name` | `string` | Control name |
| `controlId` | `string` | Standard control identifier, e.g. 1.1.1 |
| `subControlId` | `string` | — |
| `description` | `string` | — |
| `subjectArea` | `string` | — |
| `cloudProviders` | `string[]` | — |
| `adminState` | `string` | enabled \| disabled |
| `status` | `string` | Control state |
| `statusBySourceType` | `object` | Status per source type |
| `policies` | `string[]` | Mapped policy names |
| `properties` | `object` | — |
| `isManual` | `boolean` | Requires manual attestation |
| `manualComplianceDetails` | `object` | — |
| `validSourceTypes` | `string[]` | — |
| `fullControlId` | `string` | controlId plus subControlId (computed) |
| `displayName` | `string` | computed |
| `policyCount` | `integer` | computed |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List compliance controls by cloud provider

```http
GET /policies/api/v1/compliance-controls/by-cloud-provider/{cloudProvider}
```

Returns controls whose cloudProviders list contains the given provider (case-insensitive) or ALL. Not paginated.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `cloudProvider` | `string` | Cloud provider name |

**Response** `200` (`application/json`)

`ComplianceControlDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Control ID (UUID) |
| `name` | `string` | Control name |
| `controlId` | `string` | Standard control identifier, e.g. 1.1.1 |
| `subControlId` | `string` | — |
| `description` | `string` | — |
| `subjectArea` | `string` | — |
| `cloudProviders` | `string[]` | — |
| `adminState` | `string` | enabled \| disabled |
| `status` | `string` | Control state |
| `statusBySourceType` | `object` | Status per source type |
| `policies` | `string[]` | Mapped policy names |
| `properties` | `object` | — |
| `isManual` | `boolean` | Requires manual attestation |
| `manualComplianceDetails` | `object` | — |
| `validSourceTypes` | `string[]` | — |
| `fullControlId` | `string` | controlId plus subControlId (computed) |
| `displayName` | `string` | computed |
| `policyCount` | `integer` | computed |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List compliance controls by control ID

```http
GET /policies/api/v1/compliance-controls/by-control-id/{controlId}
```

Returns all compliance controls whose control ID matches the given value. Not paginated.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `controlId` | `string` | Standard control identifier (exact match) |

**Response** `200` (`application/json`)

`ComplianceControlDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Control ID (UUID) |
| `name` | `string` | Control name |
| `controlId` | `string` | Standard control identifier, e.g. 1.1.1 |
| `subControlId` | `string` | — |
| `description` | `string` | — |
| `subjectArea` | `string` | — |
| `cloudProviders` | `string[]` | — |
| `adminState` | `string` | enabled \| disabled |
| `status` | `string` | Control state |
| `statusBySourceType` | `object` | Status per source type |
| `policies` | `string[]` | Mapped policy names |
| `properties` | `object` | — |
| `isManual` | `boolean` | Requires manual attestation |
| `manualComplianceDetails` | `object` | — |
| `validSourceTypes` | `string[]` | — |
| `fullControlId` | `string` | controlId plus subControlId (computed) |
| `displayName` | `string` | computed |
| `policyCount` | `integer` | computed |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List compliance controls by status

```http
GET /policies/api/v1/compliance-controls/by-status/{status}
```

Returns all compliance controls whose status matches the given value. Not paginated.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `status` | `string` | Control status (exact match) |

**Response** `200` (`application/json`)

`ComplianceControlDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Control ID (UUID) |
| `name` | `string` | Control name |
| `controlId` | `string` | Standard control identifier, e.g. 1.1.1 |
| `subControlId` | `string` | — |
| `description` | `string` | — |
| `subjectArea` | `string` | — |
| `cloudProviders` | `string[]` | — |
| `adminState` | `string` | enabled \| disabled |
| `status` | `string` | Control state |
| `statusBySourceType` | `object` | Status per source type |
| `policies` | `string[]` | Mapped policy names |
| `properties` | `object` | — |
| `isManual` | `boolean` | Requires manual attestation |
| `manualComplianceDetails` | `object` | — |
| `validSourceTypes` | `string[]` | — |
| `fullControlId` | `string` | controlId plus subControlId (computed) |
| `displayName` | `string` | computed |
| `policyCount` | `integer` | computed |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List compliance controls by subject area

```http
GET /policies/api/v1/compliance-controls/by-subject-area/{subjectArea}
```

Returns all compliance controls whose subject area matches the given value. Not paginated.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `subjectArea` | `string` | Subject area (exact match) |

**Response** `200` (`application/json`)

`ComplianceControlDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Control ID (UUID) |
| `name` | `string` | Control name |
| `controlId` | `string` | Standard control identifier, e.g. 1.1.1 |
| `subControlId` | `string` | — |
| `description` | `string` | — |
| `subjectArea` | `string` | — |
| `cloudProviders` | `string[]` | — |
| `adminState` | `string` | enabled \| disabled |
| `status` | `string` | Control state |
| `statusBySourceType` | `object` | Status per source type |
| `policies` | `string[]` | Mapped policy names |
| `properties` | `object` | — |
| `isManual` | `boolean` | Requires manual attestation |
| `manualComplianceDetails` | `object` | — |
| `validSourceTypes` | `string[]` | — |
| `fullControlId` | `string` | controlId plus subControlId (computed) |
| `displayName` | `string` | computed |
| `policyCount` | `integer` | computed |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### Get results for a compliance control

```http
GET /policies/api/v1/compliance-controls/{id}/results
```

Returns compliance results for this control collected from all cluster, repository, and namespace compliance reports in the tenant.

**Roles:** `admin`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Control UUID |

**Response** `200` (`application/json`)

`{controlId, controlName, results: ComplianceResult[], totalResults, message}`

| Field | Type | Description |
|---|---|---|
| `controlId` | `string` | — |
| `controlName` | `string` | — |
| `totalResults` | `integer` | — |
| `results[].complianceControlName` | `string` | — |
| `results[].complianceControlId` | `string` | — |
| `results[].policy` | `string` | — |
| `results[].rule` | `string` | — |
| `results[].status` | `string` | pass \| failed \| warn \| notapplicable \| notavailable (lowercased) |
| `results[].kubeBenchIndex` | `string` | — |
| `results[].nistToCisMappings` | `object` | — |

**Errors**

- `404` — Control not found
- `500` — Retrieval failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List compliance standards by category

```http
GET /policies/api/v1/compliance-standards/by-category/{category}
```

Returns standards in the given category. Not paginated.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `category` | `string` | Category value (exact match, e.g. cis) |

**Response** `200` (`application/json`)

`ComplianceStandardDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Standard ID (UUID) |
| `name` | `string` | — |
| `version` | `string` | — |
| `description` | `string` | — |
| `type` | `string` | nirmataManaged (built-in) \| userManaged (custom) |
| `category` | `string` | Standard category, e.g. cis |
| `adminState` | `string` | enabled \| disabled |
| `score` | `integer` | Compliance score |
| `lastUpdateTime` | `integer (epoch ms)` | — |
| `k8sMinVersion` | `string` | Minimum supported Kubernetes version |
| `k8sMaxVersion` | `string` | Maximum supported Kubernetes version |
| `grade` | `string` | A-F derived from score, N/A if no score (computed) |
| `displayName` | `string` | computed |
| `controlCount` | `integer` | computed; controls are not populated by this API |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### List compliance standards by type

```http
GET /policies/api/v1/compliance-standards/by-type/{type}
```

Returns standards of the given type. Not paginated.

**Roles:** Any role with permission for this resource. See [Roles](../#roles).

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `type` | `string` | nirmataManaged \| userManaged |

**Response** `200` (`application/json`)

`ComplianceStandardDto[] (plain array)`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Standard ID (UUID) |
| `name` | `string` | — |
| `version` | `string` | — |
| `description` | `string` | — |
| `type` | `string` | nirmataManaged (built-in) \| userManaged (custom) |
| `category` | `string` | Standard category, e.g. cis |
| `adminState` | `string` | enabled \| disabled |
| `score` | `integer` | Compliance score |
| `lastUpdateTime` | `integer (epoch ms)` | — |
| `k8sMinVersion` | `string` | Minimum supported Kubernetes version |
| `k8sMaxVersion` | `string` | Maximum supported Kubernetes version |
| `grade` | `string` | A-F derived from score, N/A if no score (computed) |
| `displayName` | `string` | computed |
| `controlCount` | `integer` | computed; controls are not populated by this API |

**Errors**

- `500` — Query failed
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

### Refresh compliance results for a standard

```http
POST /policies/api/v1/compliance-standards/{id}/refresh
```

Requests an on-demand, asynchronous recomputation of the standard's compliance results across its applied clusters and repositories instead of waiting for the next background cycle. Repeated requests are debounced.

**Roles:** `admin`, `platform`, `security`

**Path parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `string` | Standard UUID |

**Response** `202` (`application/json`)

`{id, queued, lastEvaluatedAt, message?}`

| Field | Type | Description |
|---|---|---|
| `id` | `string` | Standard ID |
| `queued` | `boolean` | true if a refresh was queued; false if there are no applied sources |
| `lastEvaluatedAt` | `integer (epoch ms)` | Last evaluation time |
| `message` | `string` | Present when nothing was queued |

**Errors**

- `404` — Standard not found (code COMPLIANCE_STANDARD_NOT_FOUND)
- `429` — Refresh requested recently (REFRESH_DEBOUNCED) or already running (REFRESH_IN_PROGRESS)
- `503` — Refresh queue full (REFRESH_QUEUE_FULL)
- `500` — REFRESH_FAILED
- `401` — Not authenticated
- `403` — Caller's role is not permitted for this operation

**Example**

```json
Errors use {status, message, code}.
```


