Compliance Publishing
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 | GET | /compliance/evidence-packages |
| Declare an evidence package | POST | /compliance/evidence-packages |
| Publish a compliance snapshot | POST | /compliance/snapshots |
| Get an evidence package | GET | /compliance/evidence-packages/{id} |
| Download an evidence package | GET | /compliance/evidence-packages/{id}/download |
| Upload an evidence package part | PUT | /compliance/evidence-packages/{id}/parts/{index} |
Reference
List evidence packages
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 (>=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 authenticated403— Caller’s role is not in the allowed roles
Declare an evidence package
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 (>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_CLUSTER413— EVIDENCE_PACKAGE_TOO_LARGE422— CLUSTER_NOT_FOUND / CLUSTER_MISMATCH, SNAPSHOT_NOT_FOUND, or SNAPSHOT_MISMATCH401— Not authenticated403— Caller’s role is not in the allowed roles
Publish a compliance snapshot
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 (<=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 status409— 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 authenticated403— Caller’s role is not in the allowed roles
Get an evidence package
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_ID404— PACKAGE_NOT_FOUND401— Not authenticated403— Caller’s role is not in the allowed roles
Download an evidence package
GET /policies/api/v1/compliance/evidence-packages/{id}/download
Streams the complete evidence archive as an attachment (evidence-
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_ID404— PACKAGE_NOT_FOUND409— PACKAGE_NOT_COMPLETE, PACKAGE_FAILED or PACKAGE_DELETED401— Not authenticated403— Caller’s role is not in the allowed roles
Upload an evidence package part
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_SIZE404— PACKAGE_NOT_FOUND409— PACKAGE_FAILED or PACKAGE_DELETED401— Not authenticated403— Caller’s role is not in the allowed roles
Example
curl -X PUT -H 'Content-Type: application/octet-stream' --data-binary @part0.bin .../compliance/evidence-packages/{id}/parts/0