Security overview API
Lookup material for the Security overview endpoint in Nirmata AIControls. It returns finding counts, gateway request outcomes, the trend, and shadow AI exposure for a time window, in bounded, cached reads. A read that is slow becomes a partial section with a reason code instead of failing the whole response.
Endpoint
GET /api/v1/security-dashboard returns JSON. It runs on the same dedicated read capacity as the Cost overview API, so a slow Security read does not compete with analytics queries.
Authorization
The response is shared across viewers, so the endpoint accepts only admin and viewer tokens. A builder or developer token receives 403, and a request with missing or invalid credentials receives 401. Authorization always runs before any cached response is returned. A request that has a token query parameter is rejected with 400 before AIControls looks at the token.
Parameters
| Parameter | Description |
|---|---|
from, to | The window, as UTC RFC 3339 timestamps that end in Z and have whole seconds, for example 2026-10-10T09:14:00Z. Required. The start is included and the end is excluded. The window can be at most 31 days. |
refresh | Set to 1 to recompute instead of serving the cached response. AIControls honors a refresh at most once every 10 seconds per response, across all replicas; a refresh inside that interval returns the cached response. The Retry button on Home, Security uses it. |
user | Limit to a recorded user. |
agent | Limit to a recorded agent. |
session | Limit to a session. |
workload, cluster | Limit to a workload (namespace/Kind/name) in a cluster. Both are needed together. |
Any other parameter returns 400. The Home page offers the last 24 hours and the last 7 days. The endpoint also accepts other window lengths up to 31 days, for example from a saved link.
The response describes the window and the equal-length window before it, so each count carries a comparison.
Section status
The response is made of four independent reads. Each has a status in sections: ok, partial, or unavailable. When the status isn't ok, the section has a reason. The endpoint answers 200 even when every section fails.
| Section | Covers |
|---|---|
context | Qualification of the sources for the scope. Only reads data when session is set. |
findings | Finding groups, affected users and agents, and the trend, for the window and the previous window. |
gateway | Gateway requests, denials, response blocks, redactions, and holds, for the window and the previous window. |
shadow | Shadow AI exposure counts. |
| Reason | Meaning |
|---|---|
timeout | The read did not finish within its 2-second deadline. |
lane_busy | The dashboard read capacity was full. Retry shortly. |
not_ready | The daily rollup that serves a window longer than 25 hours is still being built (after an upgrade or a data reset) or is being repaired after a retention purge. Only the shadow read uses it. It clears on its own, usually within a few job runs. |
computing_elsewhere | Another replica is computing the response and no recent response was available to serve. |
unavailable | The data store isn't configured, or the source can't answer for this scope. |
error | An internal error, which is logged and not included in the response. |
A value that could not be read is null with reasonCode set to section_ followed by the reason, for example section_timeout. It is never reported as zero. If only the previous window of a section failed, the current value is still returned, without a comparison, and reasonCode is previous_window_ followed by the reason. A failed read also adds a dashboard_<section>_<reason> entry to completeness.gaps. When the findings or context read fails, no source is reported as succeeded: each shows queryState unavailable with reasonCode section_ followed by the reason.
Caching
Responses are cached for 45 seconds. A response in which any section is not ok is cached for only 5 seconds, so partial results recover quickly. Errors are never cached.
The cache key contains the exact from and to, and every scope filter, so two requests that differ in any of them never share an entry. The same scope sent with its parameters in a different order does share one. Concurrent requests for the same key run the reads once.
With SQLite, each process keeps its own cache. With PostgreSQL, replicas share a cache table: one replica computes a response while the others serve the previous response with stale set, for up to twice the cache lifetime, or wait for the result. cached is true when a stored response was served.
Resetting data clears the cache.
Response fields
| Field | Description |
|---|---|
scope, previousScope | The window and filters that were read, and the preceding equal window. |
asOf | When the response was computed. A cached response keeps its own asOf. |
cached, stale, staleAgeSeconds | Cache state. stale and staleAgeSeconds appear only when true. |
sections | The status of each read, as above. |
metrics | findingGroups, gatewayRequests, requestsDenied, responsesBlocked, responsesRedacted, historicalHolds, affectedUsers, affectedAgents. Each has current, previous, delta, percentChange, and reasonCode. |
trend | Up to 24 buckets of finding groups, with the previous window's count for each. |
shadow | Agents without an inventory identity, unregistered MCP servers, workloads, tools, and agents using unregistered MCP. |
sources, completeness | Which sources were read and what coverage gaps apply. |
Example
curl -s -H "Authorization: Bearer $TOKEN" \
"https://aicontrols.example.com/api/v1/security-dashboard?from=2026-10-10T09:14:00Z&to=2026-10-11T09:14:00Z"
{
"asOf": "2026-10-11T09:14:03Z",
"cached": false,
"sections": {
"context": { "status": "ok" },
"findings": { "status": "ok" },
"gateway": { "status": "ok" },
"shadow": { "status": "partial", "reason": "timeout" }
},
"metrics": {
"gatewayRequests": { "current": 1840, "previous": 1622, "delta": 218, "percentChange": 13.4, "reasonCode": "retained_history_coverage_unknown" }
},
"shadow": { "agentsWithoutInventoryIdentity": null, "reasonCodes": ["section_timeout"] }
}