{"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":{"InfraFitPeriod":{"type":"string","description":"Analysis time period for infra-fit data.","enum":["PT1H","PT4H","PT12H","P1D","P3D","P1W","P2W","P4W","P30D","P45D"],"default":"P30D"},"InfraFitNodeGroup":{"type":"object","description":"A node group with utilization metrics, cost data, and recommendations.","properties":{"id":{"type":"string","description":"Node pool identifier used in single-group requests"},"architectures":{"type":"array","items":{"$ref":"#/components/schemas/InfraFitArchitecture"},"description":"CPU architectures present in the group"},"reservations":{"type":"array","items":{"$ref":"#/components/schemas/InfraFitReservation"},"description":"Reservation types present in the group"},"autoscalerType":{"description":"Autoscaler managing this group","allOf":[{"$ref":"#/components/schemas/InfraFitAutoscalerType"}]},"nodes":{"$ref":"#/components/schemas/InfraFitNodes"},"runningMinutes":{"type":"integer","description":"Total running minutes across all nodes in the period"},"pods":{"$ref":"#/components/schemas/InfraFitPods"},"cost":{"$ref":"#/components/schemas/InfraFitCost"},"cpu":{"$ref":"#/components/schemas/InfraFitCpuStats"},"mem":{"$ref":"#/components/schemas/InfraFitMemStats"},"gpu":{"oneOf":[{"$ref":"#/components/schemas/InfraFitGpuStats"},{"type":"null"}]},"seen":{"$ref":"#/components/schemas/InfraFitSeenTimestamps"},"labels":{"type":"object","additionalProperties":{"type":"string"},"description":"Kubernetes labels on the node group"},"nodeTypes":{"type":"array","items":{"$ref":"#/components/schemas/InfraFitNodeType"},"description":"Instance types running in this group"},"recommendations":{"$ref":"#/components/schemas/InfraFitRecommendations"}},"required":["id","architectures","reservations","autoscalerType","nodes","runningMinutes","pods","cost","cpu","mem","gpu","seen","labels","nodeTypes","recommendations"]},"InfraFitArchitecture":{"type":"string","enum":["amd64","arm64"],"description":"CPU architecture of nodes in an InfraFit node group"},"InfraFitReservation":{"type":"string","enum":["on_demand","spot","reserved"],"description":"Reservation type of nodes in an InfraFit node group"},"InfraFitAutoscalerType":{"type":"string","enum":["karpenter","cluster_autoscaler","unknown"],"description":"Autoscaler managing a node group"},"InfraFitNodes":{"type":"object","description":"Node count statistics for the analysis period","properties":{"min":{"type":"integer","description":"Minimum observed node count in the period"},"max":{"type":"integer","description":"Maximum observed node count in the period"},"avg":{"type":"number","format":"double","description":"Average node count in the period"}},"required":["min","max","avg"]},"InfraFitPods":{"type":"object","description":"Pod count statistics for the node group","properties":{"capacity":{"type":"integer","description":"Maximum number of pods the node group can schedule"},"allocatable":{"type":"integer","description":"Maximum number of pods allocatable on the node group (Kubernetes limit per node × node count)"},"avgCount":{"type":"number","format":"double","description":"Average number of pods running in the group"}},"required":["capacity","allocatable","avgCount"]},"InfraFitCost":{"type":"object","description":"Cost breakdown for the analysis period","properties":{"hourly":{"$ref":"#/components/schemas/Money"},"timeframe":{"$ref":"#/components/schemas/Money"},"idle":{"$ref":"#/components/schemas/InfraFitCostIdle"}},"required":["hourly","timeframe","idle"]},"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"]},"InfraFitCostIdle":{"type":"object","description":"Idle cost breakdown by resource type","properties":{"total":{"$ref":"#/components/schemas/Money"},"cpu":{"$ref":"#/components/schemas/Money"},"gpu":{"oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"mem":{"$ref":"#/components/schemas/Money"}},"required":["total","cpu","gpu","mem"]},"InfraFitCpuStats":{"type":"object","description":"CPU utilization statistics (cores)","properties":{"requested":{"type":"object","description":"CPU cores requested statistics","properties":{"minCores":{"type":"number","format":"double","description":"Minimum CPU cores requested over the requested period"},"maxCores":{"type":"number","format":"double","description":"Maximum CPU cores requested over the requested period"},"avgCores":{"type":"number","format":"double","description":"Time-weighted median CPU cores requested over the requested period (single aggregate, not per-datapoint)"},"p80Cores":{"type":"number","format":"double","description":"80th percentile CPU cores requested over the requested period"},"p95Cores":{"type":"number","format":"double","description":"95th percentile CPU cores requested over the requested period"},"p99Cores":{"type":"number","format":"double","description":"99th percentile CPU cores requested over the requested period"},"p999Cores":{"type":"number","format":"double","description":"99.9th percentile CPU cores requested over the requested period"}},"required":["avgCores","minCores","maxCores","p80Cores","p95Cores","p99Cores","p999Cores"]},"used":{"type":"object","description":"CPU cores used statistics","properties":{"minCores":{"type":"number","format":"double","description":"Minimum CPU cores used over the requested period"},"maxCores":{"type":"number","format":"double","description":"Maximum CPU cores used over the requested period"},"avgCores":{"type":"number","format":"double","description":"Time-weighted median CPU cores used over the requested period (single aggregate, not per-datapoint)"},"p80Cores":{"type":"number","format":"double","description":"80th percentile CPU cores used over the requested period"},"p95Cores":{"type":"number","format":"double","description":"95th percentile CPU cores used over the requested period"},"p99Cores":{"type":"number","format":"double","description":"99th percentile CPU cores used over the requested period"},"p999Cores":{"type":"number","format":"double","description":"99.9th percentile CPU cores used over the requested period"}},"required":["avgCores","minCores","maxCores","p80Cores","p95Cores","p99Cores","p999Cores"]},"idleCores":{"type":"number","format":"double","description":"Idle (unallocated) CPU cores — max(0, allocatable − avg used)"}},"required":["requested","used","idleCores"]},"InfraFitMemStats":{"type":"object","description":"Memory utilization statistics (MiB)","properties":{"requested":{"type":"object","description":"Memory requested statistics","properties":{"minMiB":{"type":"number","format":"double","description":"Minimum memory requested in MiB over the requested period"},"maxMiB":{"type":"number","format":"double","description":"Maximum memory requested in MiB over the requested period"},"avgMiB":{"type":"number","format":"double","description":"Time-weighted median memory requested in MiB over the requested period (single aggregate, not per-datapoint)"},"p80MiB":{"type":"number","format":"double","description":"80th percentile memory requested in MiB over the requested period"},"p95MiB":{"type":"number","format":"double","description":"95th percentile memory requested in MiB over the requested period"},"p99MiB":{"type":"number","format":"double","description":"99th percentile memory requested in MiB over the requested period"},"p999MiB":{"type":"number","format":"double","description":"99.9th percentile memory requested in MiB over the requested period"}},"required":["avgMiB","minMiB","maxMiB","p80MiB","p95MiB","p99MiB","p999MiB"]},"used":{"type":"object","description":"Memory used statistics","properties":{"minMiB":{"type":"number","format":"double","description":"Minimum memory used in MiB over the requested period"},"maxMiB":{"type":"number","format":"double","description":"Maximum memory used in MiB over the requested period"},"avgMiB":{"type":"number","format":"double","description":"Time-weighted median memory used in MiB over the requested period (single aggregate, not per-datapoint)"},"p80MiB":{"type":"number","format":"double","description":"80th percentile memory used in MiB over the requested period"},"p95MiB":{"type":"number","format":"double","description":"95th percentile memory used in MiB over the requested period"},"p99MiB":{"type":"number","format":"double","description":"99th percentile memory used in MiB over the requested period"},"p999MiB":{"type":"number","format":"double","description":"99.9th percentile memory used in MiB over the requested period"}},"required":["avgMiB","minMiB","maxMiB","p80MiB","p95MiB","p99MiB","p999MiB"]},"idleMiB":{"type":"number","format":"double","description":"Idle (unallocated) memory in MiB — max(0, allocatable − avg used)"}},"required":["requested","used","idleMiB"]},"InfraFitGpuStats":{"type":"object","description":"GPU utilization statistics","properties":{"idle":{"$ref":"#/components/schemas/InfraFitIdleGpu"},"requested":{"$ref":"#/components/schemas/InfraFitGpuSample"},"used":{"$ref":"#/components/schemas/InfraFitGpuSample"},"architectures":{"type":"array","items":{"type":"string"},"description":"GPU architectures present in the group (e.g. ampere, hopper), sorted"},"sharingType":{"type":"array","items":{"type":"string"},"description":"Active GPU sharing mechanisms in lower_snake_case (e.g. \"full\", \"time_slicing\", \"mps\", \"mig\")"}},"required":["idle","architectures","requested","used","sharingType"]},"InfraFitIdleGpu":{"type":"object","description":"Idle (unallocated) GPU resources","properties":{"units":{"type":"number","format":"double","description":"Idle (unallocated) GPU units — max(0, allocatable − avg used)"},"memoryMiB":{"type":"number","format":"double","description":"Idle (unallocated) GPU memory in MiB — max(0, capacity − avg used)"}},"required":["units","memoryMiB"]},"InfraFitGpuSample":{"type":"object","description":"GPU sample statistics (time-weighted medians and percentiles)","properties":{"avgUnits":{"type":"number","format":"double","description":"Time-weighted median GPU units"},"minUnits":{"type":"number","format":"double","description":"Minimum GPU units over the period"},"maxUnits":{"type":"number","format":"double","description":"Maximum GPU units over the period"},"p80Units":{"type":"number","format":"double","description":"80th percentile GPU units over the period"},"p95Units":{"type":"number","format":"double","description":"95th percentile GPU units over the period"},"p99Units":{"type":"number","format":"double","description":"99th percentile GPU units over the period"},"p999Units":{"type":"number","format":"double","description":"99.9th percentile GPU units over the period"},"avgMemoryMiB":{"type":"number","format":"double","description":"Time-weighted median GPU memory in MiB"},"minMemoryMiB":{"type":"number","format":"double","description":"Minimum GPU memory in MiB over the period"},"maxMemoryMiB":{"type":"number","format":"double","description":"Maximum GPU memory in MiB over the period"},"p80MemoryMiB":{"type":"number","format":"double","description":"80th percentile GPU memory in MiB over the period"},"p95MemoryMiB":{"type":"number","format":"double","description":"95th percentile GPU memory in MiB over the period"},"p99MemoryMiB":{"type":"number","format":"double","description":"99th percentile GPU memory in MiB over the period"},"p999MemoryMiB":{"type":"number","format":"double","description":"99.9th percentile GPU memory in MiB over the period"}},"required":["avgUnits","minUnits","maxUnits","p80Units","p95Units","p99Units","p999Units","avgMemoryMiB","minMemoryMiB","maxMemoryMiB","p80MemoryMiB","p95MemoryMiB","p99MemoryMiB","p999MemoryMiB"]},"InfraFitSeenTimestamps":{"type":"object","description":"First and last observed timestamps","properties":{"firstTime":{"type":"string","format":"date-time","description":"When first observed (RFC 3339 UTC)"},"lastTime":{"type":"string","format":"date-time","description":"When last observed (RFC 3339 UTC)"}},"required":["firstTime","lastTime"]},"InfraFitNodeType":{"type":"object","description":"Instance type breakdown within a node group","properties":{"id":{"type":"string","description":"Instance type identifier (matches instance.type)"},"instance":{"$ref":"#/components/schemas/InfraFitInstanceInfo"},"isSpot":{"type":"boolean","description":"Whether this instance type runs on spot/preemptible capacity"},"nodes":{"$ref":"#/components/schemas/InfraFitNodes"},"cost":{"$ref":"#/components/schemas/InfraFitCost"},"runningMinutes":{"type":"integer","description":"Total running minutes across all nodes of this instance type in the period"},"pods":{"$ref":"#/components/schemas/InfraFitPods"},"cpu":{"$ref":"#/components/schemas/InfraFitCpuStats"},"mem":{"$ref":"#/components/schemas/InfraFitMemStats"},"gpu":{"oneOf":[{"$ref":"#/components/schemas/InfraFitNodeTypeGpu"},{"type":"null"}]},"seen":{"$ref":"#/components/schemas/InfraFitSeenTimestamps"}},"required":["id","instance","isSpot","nodes","cost","runningMinutes","pods","cpu","mem","gpu","seen"]},"InfraFitInstanceInfo":{"type":"object","description":"Instance type identity and capacity","properties":{"type":{"type":"string","description":"Cloud instance type identifier (e.g. m5.2xlarge)"},"family":{"type":"string","description":"Instance family prefix shared by related instance types (e.g. m5)"},"architecture":{"oneOf":[{"$ref":"#/components/schemas/InfraFitArchitecture"},{"type":"null"}],"description":"CPU architecture of this instance type (null when unknown)"},"cpu":{"$ref":"#/components/schemas/InfraFitInstanceCpu"},"mem":{"$ref":"#/components/schemas/InfraFitInstanceMem"},"gpu":{"oneOf":[{"$ref":"#/components/schemas/InfraFitInstanceGpu"},{"type":"null"}],"description":"GPU capacity, null for non-GPU instance types"}},"required":["type","family","architecture","cpu","mem","gpu"]},"InfraFitInstanceCpu":{"type":"object","description":"CPU capacity for an instance type","properties":{"capacityCores":{"type":"number","format":"double","description":"vCPU count per instance"},"allocatableCores":{"type":"number","format":"double","description":"Allocatable vCPU per instance after Kubernetes system-reserved resources are subtracted"}},"required":["capacityCores","allocatableCores"]},"InfraFitInstanceMem":{"type":"object","description":"Memory capacity for an instance type","properties":{"capacityMiB":{"type":"number","format":"double","description":"Memory capacity per instance in MiB"},"allocatableMiB":{"type":"number","format":"double","description":"Allocatable memory per instance in MiB after Kubernetes system-reserved resources are subtracted"}},"required":["capacityMiB","allocatableMiB"]},"InfraFitInstanceGpu":{"type":"object","description":"GPU capacity for an instance type","properties":{"capacityUnits":{"type":"number","format":"double","description":"GPU unit count per instance"},"allocatableUnits":{"type":"number","format":"double","description":"Allocatable GPU units per instance after Kubernetes system-reserved resources are subtracted"},"memCapacityMiB":{"type":"number","format":"double","description":"Total GPU memory capacity per instance in MiB"}},"required":["capacityUnits","allocatableUnits","memCapacityMiB"]},"InfraFitNodeTypeGpu":{"type":"object","description":"GPU stats for an instance type","properties":{"idle":{"$ref":"#/components/schemas/InfraFitIdleGpu"},"requested":{"$ref":"#/components/schemas/InfraFitGpuSample"},"used":{"$ref":"#/components/schemas/InfraFitGpuSample"},"architectures":{"type":"array","items":{"type":"string"},"description":"GPU architectures present in the instance type (e.g. ampere, hopper), sorted"}},"required":["idle","requested","used","architectures"]},"InfraFitRecommendations":{"description":"Discriminated recommendations object. Routed by autoscalerType: cluster_autoscaler/managed → standard, karpenter → karpenter. Always present, never omitted.\n","oneOf":[{"$ref":"#/components/schemas/InfraFitStandardRecommendations"},{"$ref":"#/components/schemas/InfraFitKarpenterRecommendations"}],"discriminator":{"propertyName":"type","mapping":{"standard":"#/components/schemas/InfraFitStandardRecommendations","karpenter":"#/components/schemas/InfraFitKarpenterRecommendations"}}},"InfraFitStandardRecommendations":{"type":"object","description":"Recommendations for Cluster-Autoscaler and managed node groups","properties":{"type":{"type":"string","enum":["standard"]},"hasChanges":{"type":"boolean","description":"Whether any instance type changes are recommended"},"nodeTypes":{"type":"array","items":{"$ref":"#/components/schemas/InfraFitRecommendedNodeType"},"description":"Recommended instance types, capped at recommendationLimit"}},"required":["type","hasChanges","nodeTypes"]},"InfraFitRecommendedNodeType":{"type":"object","description":"A recommended instance type replacement","properties":{"id":{"type":"string","description":"Unique identifier for this recommendation entry (matches instanceType)"},"instanceType":{"type":"string","description":"Recommended cloud instance type identifier (e.g. m6g.xlarge)"},"instanceFamily":{"type":"string","description":"Instance family prefix of the recommended type (e.g. m6g)"},"hourlyCost":{"$ref":"#/components/schemas/Money"},"estimatedSavings":{"$ref":"#/components/schemas/Money"},"estimatedSavingsPct":{"type":"number","format":"double","description":"Savings as percentage of current group cost"},"nodeCount":{"type":"integer","description":"Recommended number of nodes of this type"}},"required":["id","instanceType","instanceFamily","hourlyCost","estimatedSavings","estimatedSavingsPct","nodeCount"]},"InfraFitKarpenterRecommendations":{"type":"object","description":"Recommendations for Karpenter-managed node pools","properties":{"type":{"type":"string","enum":["karpenter"]},"hasChanges":{"type":"boolean","description":"Whether any configuration changes are recommended"},"currentConfig":{"type":"object","description":"Current raw Karpenter NodePool CR (full JSON)"},"recommendedConfig":{"type":"object","description":"Patched NodePool CR with all recommended changes applied"},"changes":{"type":"array","items":{"$ref":"#/components/schemas/InfraFitKarpenterChange"},"description":"Structured change set (always full, not capped by recommendationLimit)"}},"required":["type","hasChanges","currentConfig","recommendedConfig","changes"]},"InfraFitKarpenterChange":{"type":"object","description":"A single Karpenter configuration change recommendation","properties":{"id":{"type":"string","description":"Unique identifier for this change entry (matches path)"},"title":{"type":"string","description":"Human-readable change title"},"path":{"type":"string","description":"JSONPath (RFC 9535 syntax) into the NodePool CR. Nested keys and array equality-filter selectors are supported, e.g. \".spec.disruption.consolidationPolicy\" or \".spec.template.spec.requirements[?(@.key==\\\"node.kubernetes.io/instance-type\\\")]\".\n"},"operation":{"type":"string","description":"Change operation: \"replace\", \"merge\", or \"remove\". An empty operation is treated as \"replace\"; a replace with a null recommendedValue removes the field.\n"},"currentValue":{"description":"Current value at the path (string, number, boolean, array, object, or null)"},"recommendedValue":{"description":"Recommended value (string, number, boolean, array, object, or null)"},"rationale":{"type":"string","description":"Human-readable explanation for this change"}},"required":["id","title","path","operation","currentValue","recommendedValue","rationale"]},"InfraFitMeta":{"type":"object","description":"Response metadata for InfraFit endpoints","properties":{"timeframe":{"type":"string","description":"Analysis period echoed from the period query parameter (e.g. \"P30D\")"},"pagination":{"description":"Cursor-based pagination metadata. Present in list responses; omitted for single-item responses.","allOf":[{"$ref":"#/components/schemas/PaginationMeta"}]}},"required":["timeframe"]},"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"]},"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}/node-groups/{nodeGroupName}":{"get":{"operationId":"GetInfraFitNodeGroup","tags":["InfraFit"],"summary":"Get a single node group with infra-fit data and recommendations","description":"Returns a single node group by name. Same shape as one item from the list endpoint. Returns 404 if the group does not exist in the cluster.\n","parameters":[{"name":"clusterUid","in":"path","required":true,"schema":{"type":"string"},"description":"Cluster unique identifier"},{"name":"nodeGroupName","in":"path","required":true,"schema":{"type":"string"},"description":"Node group name"},{"name":"period","in":"query","schema":{"allOf":[{"$ref":"#/components/schemas/InfraFitPeriod"}],"default":"P30D"},"description":"Window for the node group's own utilization/idle-cost metrics (CPU, memory, running minutes, unrecognized-group detection). Does not affect recommendation savings/pricing math, which always uses a fixed 30-day window regardless of this value. Default P30D.\n"},{"name":"recommendationLimit","in":"query","schema":{"type":"integer","default":3,"minimum":1,"maximum":20},"description":"Max recommended node types per group (Karpenter always returns full change set)"}],"responses":{"200":{"description":"Single node group","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InfraFitNodeGroup"},"meta":{"$ref":"#/components/schemas/InfraFitMeta"}},"required":["data","meta"]}}}},"400":{"description":"Invalid parameters","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Cluster or node group not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal server error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}}}