{"openapi":"3.1.0","info":{"title":"Perfectscale API - OpenAPI 3.1","version":"v1"},"servers":[{"url":"https://api.app.perfectscale.io/public/v1"}],"security":[{"auth":[]}],"components":{"securitySchemes":{"auth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"UnevictablePod":{"type":"object","description":"A single unevictable pod from the latest pre-computed snapshot for the cluster. Returned both as a list item and as the single-pod detail response — the detail response additionally populates siblingPodNames.\n","required":["name","namespace","id","workload","reasons","phase","startTime"],"properties":{"name":{"type":"string"},"namespace":{"type":"string"},"id":{"type":"string"},"workload":{"$ref":"#/components/schemas/UnevictableWorkloadRef"},"reasons":{"type":"array","description":"Empty = pod is evictable; non-empty = unevictable.","items":{"$ref":"#/components/schemas/UnevictableReason"}},"phase":{"type":"string"},"startTime":{"type":"string","format":"date-time","description":"Pod creation timestamp (RFC 3339 UTC)."},"labels":{"type":"object","additionalProperties":{"type":"string"}},"annotations":{"type":"object","additionalProperties":{"type":"string"}},"spec":{"$ref":"#/components/schemas/UnevictablePodSpec"},"blockedNodeCount":{"type":"integer","description":"Number of nodes this pod blocks from scaling down."},"blockedNodes":{"type":"array","description":"Names of the nodes this pod pins.","items":{"type":"string"}},"blockedCostHourly":{"description":"Hourly cost of the blocked nodes (Total Blocked Cost). Filtering (`blockedCostHourly:gte:<amount>`) and sorting compare numerically on the underlying cost value, not the serialized `Money` object.\n","allOf":[{"$ref":"#/components/schemas/Money"}]},"clusterUid":{"type":"string"},"mute":{"type":"boolean","description":"True when the pod's workload is muted by a dismissal rule, muting all of its reasons."},"siblingPodNames":{"type":"array","description":"Names of other pods of the same workload id in this snapshot. Populated only by the single-pod detail endpoint.","items":{"type":"string"}}}},"UnevictableWorkloadRef":{"type":"object","description":"Identity of the pod's top-level owning workload.","required":["id","type"],"properties":{"id":{"type":"string","description":"Canonical workload id (namespace-kind-name) of the top-level controller."},"name":{"type":"string","description":"Denormalized workload name for display."},"type":{"type":"string","description":"Kubernetes workload kind (e.g. Deployment, StatefulSet, DaemonSet)."}}},"UnevictableReason":{"type":"object","required":["reason","details"],"properties":{"reason":{"type":"string","description":"Short reason code (e.g. \"Topology-Locked\", \"PDB Violation\")."},"reasonCode":{"oneOf":[{"$ref":"#/components/schemas/UnevictableReasonCode"},{"type":"null"}],"description":"Stable slug derived from the display reason. Null for unrecognised reasons."},"details":{"type":"string","description":"Human-readable explanation."},"remediation":{"$ref":"#/components/schemas/UnevictableRemediation"},"mute":{"type":"boolean","description":"True when this reason is covered by a dismissal rule and therefore muted."},"mutedByRule":{"oneOf":[{"$ref":"#/components/schemas/UnevictableMutedByRule"},{"type":"null"}]}}},"UnevictableReasonCode":{"type":"string","description":"Stable canonical identifier for an unevictable reason. Decoupled from the mutable display string.","enum":["opt_out_annotation","pod_disruption_budget","strict_affinity","strict_anti_affinity","topology_locked","taints_not_tolerated","local_node_storage","static_mirror_pod"]},"UnevictableRemediation":{"type":"object","description":"Reason-specific fix. current/recommended spec and the yaml diff are null for partial/informational cases.\n","required":["fixSummary","risk","confidence"],"properties":{"fixSummary":{"type":"string","description":"One-line plain-language fix."},"risk":{"type":"string","enum":["none","low","medium","high"]},"confidence":{"type":"string","enum":["low","medium","high"]},"currentSpec":{"type":["string","null"],"description":"Rendered YAML of the current spec; null for partial/informational cases."},"recommendedSpec":{"type":["string","null"],"description":"Rendered YAML of the recommended spec; null for partial/informational cases."},"yamlDiff":{"type":["string","null"],"description":"Unified diff string; null when there is no clean diff."}}},"UnevictableMutedByRule":{"type":"object","description":"Summary of the dismissal rule that suppressed this finding.","required":["createdBy","createTime"],"properties":{"note":{"type":["string","null"]},"createdBy":{"type":"string","description":"Email of the user who created the rule."},"createTime":{"type":"string","format":"date-time","description":"When the rule was created (RFC 3339 UTC)"}}},"UnevictablePodSpec":{"type":"object","description":"Raw Kubernetes PodSpec fields used by detection, verbatim from the pod.","properties":{"node":{"type":["string","null"],"description":"Node the pod is scheduled on (spec.nodeName); null for Pending pods."},"nodeGroup":{"type":"string","description":"Node group the pod's node belongs to; empty if the node's node group is unresolved."},"priority":{"type":"integer","description":"Kubernetes pod scheduling priority (spec.priority, from the pod's PriorityClass)."},"nodeSelector":{"type":"object","additionalProperties":{"type":"string"}},"affinity":{"allOf":[{"$ref":"#/components/schemas/UnevictablePodAffinity"}]},"tolerations":{"type":"array","items":{"$ref":"#/components/schemas/UnevictablePodToleration"}},"containers":{"type":"array","items":{"$ref":"#/components/schemas/UnevictablePodContainer"}},"volumes":{"type":"array","items":{"$ref":"#/components/schemas/UnevictablePodVolume"}},"topologySpreadConstraints":{"type":"array","items":{"$ref":"#/components/schemas/UnevictablePodTopologySpreadConstraint"}},"ownerReferences":{"type":"array","description":"The pod's metadata.ownerReferences, as the subset of fields described above. Omitted when the pod has no owner (e.g. static/mirror pods — see the static_mirror_pod reason code).\n","items":{"$ref":"#/components/schemas/UnevictablePodOwnerReference"}}}},"UnevictablePodAffinity":{"type":"object","description":"Simplified affinity block — only the sub-fields used by detection are modelled.","properties":{"nodeAffinity":{"type":"object","additionalProperties":true},"podAffinity":{"type":"object","additionalProperties":true},"podAntiAffinity":{"type":"object","additionalProperties":true}}},"UnevictablePodToleration":{"type":"object","required":["key","operator","effect"],"properties":{"key":{"type":"string"},"operator":{"type":"string"},"value":{"type":"string"},"effect":{"type":"string"}}},"UnevictablePodContainer":{"type":"object","required":["name","image"],"properties":{"name":{"type":"string"},"image":{"type":"string"},"cpuRequestCores":{"type":["number","null"],"format":"double","description":"CPU requested for the container, in cores; null if unset."},"cpuLimitCores":{"type":["number","null"],"format":"double","description":"CPU limit for the container, in cores; null if unset."},"memoryRequestMiB":{"type":["number","null"],"format":"double","description":"Memory requested for the container, in MiB; null if unset."},"memoryLimitMiB":{"type":["number","null"],"format":"double","description":"Memory limit for the container, in MiB; null if unset."},"gpuRequest":{"type":["integer","null"],"description":"GPU units requested for the container (e.g. nvidia.com/gpu); null if unset. GPU requests/limits are always whole units."},"gpuLimit":{"type":["integer","null"],"description":"GPU units the container is limited to (e.g. nvidia.com/gpu); null if unset. GPU requests/limits are always whole units."}}},"UnevictablePodVolume":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"hostPath":{"type":"string"},"emptyDir":{"type":"string"},"pvcClaimName":{"type":"string"}}},"UnevictablePodTopologySpreadConstraint":{"type":"object","required":["maxSkew","topologyKey","whenUnsatisfiable"],"properties":{"maxSkew":{"type":"integer","format":"int32"},"topologyKey":{"type":"string"},"whenUnsatisfiable":{"type":"string"},"labelSelector":{"type":"object","additionalProperties":true}}},"UnevictablePodOwnerReference":{"type":"object","description":"A subset of the pod's Kubernetes OwnerReference fields (apiVersion, kind, name, controller) used for ownership detection; not a complete copy of metadata.ownerReferences (e.g. uid is omitted).\n","required":["apiVersion","kind","name"],"properties":{"apiVersion":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"controller":{"type":["boolean","null"],"description":"True when this reference is the pod's managing controller."}}},"Money":{"type":"object","description":"Monetary amount with currency","properties":{"amount":{"type":"string","description":"Decimal string representation of the amount (e.g. \"100.50\")"},"currency":{"type":"string","description":"ISO 4217 currency code (e.g. \"USD\")"}},"required":["amount","currency"]},"PaginationMeta":{"type":"object","description":"Cursor-based pagination metadata for list responses.","properties":{"next":{"type":["string","null"],"description":"Opaque cursor token for the next page, or null if this is the last page"},"prev":{"type":["string","null"],"description":"Opaque cursor token for the previous page, or null if this is the first page"},"pageSize":{"type":"integer","description":"Number of items per page used for this response"}},"required":["next","prev","pageSize"]},"UnevictableSummary":{"type":"object","description":"Aggregate pod/node counts for the snapshot backing the listing response.","required":["totalPods","unevictablePods","mute","totalNodes"],"properties":{"totalPods":{"type":"integer","description":"Total number of pods observed in the snapshot."},"unevictablePods":{"type":"integer","description":"Number of pods detected as unevictable in the snapshot."},"mute":{"type":"integer","description":"Number of unevictable findings muted by a dismissal rule in this snapshot."},"totalNodes":{"type":"integer","description":"Total number of nodes observed in the snapshot."},"autoscalerType":{"type":"string","description":"Detected autoscaler, rolled up from per-node detection (canonical\nvalues, e.g. \"cluster_autoscaler\", \"karpenter\", \"castai\",\n\"eks_auto_mode\"). Omitted if not detected. Results written before\nPSD-9016 may carry the legacy hyphenated \"cluster-autoscaler\"."}}},"UnevictablePodsStatus":{"type":"object","description":"Snapshot processing status, returned on 202 (still processing) and 422 (processing failed) instead of pod/report data.\n","properties":{"status":{"type":"string","enum":["processing","failed"]}},"required":["status"]},"Problem":{"type":"object","description":"RFC 9457 Problem Details error body, served as `application/problem+json`. `code` is a stable machine-readable identifier (e.g. `filter_key_not_supported`, `not_found`, `internal_error`); `retryable` indicates whether retrying the same request may succeed without client-side changes.\n","properties":{"type":{"type":"string","description":"URI reference identifying the problem type."},"title":{"type":"string","description":"Short, human-readable summary of the problem type."},"status":{"type":"integer","description":"HTTP status code generated by the origin server for this occurrence of the problem."},"detail":{"type":"string","description":"Human-readable explanation specific to this occurrence of the problem."},"instance":{"type":"string","description":"URI reference identifying the specific occurrence of the problem."},"code":{"type":"string","description":"Stable machine-readable error code."},"retryable":{"type":"boolean","description":"Whether retrying the same request may succeed without client-side changes."}},"required":["type","title","status","code","retryable"]}}},"paths":{"/clusters/{clusterUid}/unevictable-pods":{"get":{"operationId":"ListUnevictablePods","tags":["Unevictable"],"summary":"List unevictable pods in a cluster","description":"Returns the pods from the latest pre-computed unevictable-pods snapshot for the cluster (no request-time recompute). Tenant resolved from the auth token; a cluster belonging to another tenant returns 404.\n","parameters":[{"name":"clusterUid","in":"path","required":true,"schema":{"type":"string"},"description":"Cluster unique identifier"},{"name":"filter","in":"query","required":false,"schema":{"type":"string"},"description":"Composite filter expression: clauses of the form `key[:op]:value` joined by `|` (AND); `op` is optional and defaults to `eq`; comma-separate values within a clause for an IN match, e.g. `filter=namespace:payments|blockedCostHourly:gte:10`. Supported keys for this endpoint: `namespace` (eq), `blockedCostHourly` (eq/gte/lte, compares numerically on the underlying cost value, not the serialized `Money` object), `reasonCode` (eq), `nodeGroup` (eq), `labels.<key>` (eq). `labels.<key>` matches against the pod's Kubernetes labels — for example `filter=labels.team:payments` matches pods labeled `team=payments`. An unsupported key returns 400 with `code: \"filter_key_not_supported\"`. Mute-status filtering is handled by the separate `mute` parameter, not this filter.\n"},{"name":"mute","in":"query","required":false,"schema":{"type":"string","enum":["exclude","include","only"],"default":"exclude"},"description":"Muted-finding handling: `exclude` (default) hides pods whose workload has a dismissal rule; `include` returns all pods, flagging muted ones via `mute`/`mutedByRule` on each reason; `only` returns exclusively muted pods, for auditing what's been silenced.\n"},{"name":"sortBy","in":"query","required":false,"schema":{"type":"string","enum":["blockedCostHourly"]},"description":"Field to sort results by. `blockedCostHourly` sorts numerically on the underlying cost value, not the serialized `Money` object. Unset falls back to a stable but unspecified order (currently pod UID). Ties are always broken by UID for stable pagination.\n"},{"name":"sortOrder","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"description":"Sort direction, applied only when sortBy is set."},{"name":"pageSize","in":"query","schema":{"type":"integer","default":50,"minimum":1,"maximum":500},"description":"Number of items per page (default 50, max 500)"},{"name":"pageToken","in":"query","schema":{"type":"string"},"description":"Opaque cursor token for pagination"}],"responses":{"200":{"description":"List of unevictable pods","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/UnevictablePod"}},"meta":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/PaginationMeta"},"snapshotTime":{"type":"string","format":"date-time","description":"Timestamp (RFC 3339, UTC) of the snapshot the data was read from"},"algorithmVersion":{"type":"string","description":"Version of the unevictable-detection algorithm/library used to produce the snapshot"},"summary":{"$ref":"#/components/schemas/UnevictableSummary"}},"required":["pagination","snapshotTime","algorithmVersion","summary"]}},"required":["data","meta"]}}}},"202":{"description":"Raw snapshot exists but is still being processed","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/UnevictablePodsStatus"}},"required":["data"]}}}},"400":{"description":"Invalid parameters","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Cluster or snapshot not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Raw snapshot exists but processing failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal server error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}}}