This document provides detailed information about the HyperFleet API resources, including endpoints, request/response formats, and usage patterns.
All API endpoints require a valid JWT bearer token when authentication is enabled (the default in production). Requests without a valid token receive 401 Unauthorized. See authentication.md for configuration details, token format, and caller identity resolution.
Mutating requests (POST, PATCH, PUT, DELETE) additionally require a resolvable caller identity — either from a JWT claim or an identity header — which is recorded in audit fields (created_by, updated_by, deleted_by). Read requests (GET, LIST) are allowed without caller identity.
Note: The API does not enforce role-based access control (RBAC). Any authenticated caller can invoke any endpoint, including destructive operations like force-delete. Access control should be enforced at the infrastructure layer (e.g., ingress policies, gateway authorization).
GET /api/hyperfleet/v1/clusters
POST /api/hyperfleet/v1/clusters
GET /api/hyperfleet/v1/clusters/{cluster_id}
PATCH /api/hyperfleet/v1/clusters/{cluster_id}
DELETE /api/hyperfleet/v1/clusters/{cluster_id}
POST /api/hyperfleet/v1/clusters/{cluster_id}/force-delete
GET /api/hyperfleet/v1/clusters/{cluster_id}/statuses
PUT /api/hyperfleet/v1/clusters/{cluster_id}/statuses
POST /api/hyperfleet/v1/clusters
Request Body:
{
"kind": "Cluster",
"name": "my-cluster",
"spec": {},
"labels": {
"environment": "production"
}
}Response (201 Created):
JSON response 201 created
{
"kind": "Cluster",
"id": "2abc123...",
"href": "/api/hyperfleet/v1/clusters/2abc123...",
"name": "my-cluster",
"generation": 1,
"spec": {},
"labels": {
"environment": "production"
},
"created_time": "2025-01-01T00:00:00Z",
"updated_time": "2025-01-01T00:00:00Z",
"created_by": "user@example.com",
"updated_by": "user@example.com",
"status": {
"conditions": [
{
"type": "Reconciled",
"status": "False",
"reason": "ReconciledMissingAdapters",
"message": "Required adapters have not yet reported status",
"observed_generation": 1,
"created_time": "2025-01-01T00:00:00Z",
"last_updated_time": "2025-01-01T00:00:00Z",
"last_transition_time": "2025-01-01T00:00:00Z"
},
{
"type": "LastKnownReconciled",
"status": "False",
"reason": "AdaptersMissingReports",
"message": "Required adapters have not yet reported status",
"observed_generation": 1,
"created_time": "2025-01-01T00:00:00Z",
"last_updated_time": "2025-01-01T00:00:00Z",
"last_transition_time": "2025-01-01T00:00:00Z"
},
]
}
}Note: Status initially has Reconciled=False and LastKnownReconciled=False conditions until adapters report status.
GET /api/hyperfleet/v1/clusters/{cluster_id}
Response (200 OK):
JSON response
{
"kind": "Cluster",
"id": "2abc123...",
"href": "/api/hyperfleet/v1/clusters/2abc123...",
"name": "my-cluster",
"generation": 1,
"spec": {},
"labels": {
"environment": "production"
},
"created_time": "2025-01-01T00:00:00Z",
"updated_time": "2025-01-01T00:00:00Z",
"created_by": "user@example.com",
"updated_by": "user@example.com",
"status": {
"conditions": [
{
"type": "Reconciled",
"status": "True",
"reason": "ReconciledAll",
"message": "All required adapters reported Available=True or Finalized=True at the current generation",
"observed_generation": 1,
"created_time": "2025-01-01T00:00:00Z",
"last_updated_time": "2025-01-01T00:00:00Z",
"last_transition_time": "2025-01-01T00:00:00Z"
},
{
"type": "LastKnownReconciled",
"status": "True",
"reason": "AllAdaptersReconciled",
"message": "All required adapters report Available=True for the tracked generation",
"observed_generation": 1,
"created_time": "2025-01-01T00:00:00Z",
"last_updated_time": "2025-01-01T00:00:00Z",
"last_transition_time": "2025-01-01T00:00:00Z"
},
]
}
}GET /api/hyperfleet/v1/clusters?page=1&pageSize=10
Response (200 OK):
{
"kind": "ClusterList",
"page": 1,
"size": 10,
"total": 100,
"items": [
{
"kind": "Cluster",
"id": "2abc123...",
"name": "my-cluster",
...
}
]
}PUT /api/hyperfleet/v1/clusters/{cluster_id}/statuses
Adapters use this endpoint to report their status.
Request Body:
JSON response
{
"adapter": "validator",
"observed_generation": 1,
"observed_time": "2025-01-01T10:00:00Z",
"conditions": [
{
"type": "Available",
"status": "True",
"reason": "AllValidationsPassed",
"message": "All validations passed"
},
{
"type": "Applied",
"status": "True",
"reason": "ValidationJobApplied",
"message": "Validation job applied successfully"
},
{
"type": "Health",
"status": "True",
"reason": "OperationsCompleted",
"message": "All adapter operations completed successfully"
}
],
"data": {
"job_name": "validator-job-abc123",
"attempt": 1
}
}Response (201 Created):
JSON response
{
"adapter": "validator",
"observed_generation": 1,
"conditions": [
{
"type": "Available",
"status": "True",
"reason": "AllValidationsPassed",
"message": "All validations passed",
"last_transition_time": "2025-01-01T10:00:00Z"
},
{
"type": "Applied",
"status": "True",
"reason": "ValidationJobApplied",
"message": "Validation job applied successfully",
"last_transition_time": "2025-01-01T10:00:00Z"
},
{
"type": "Health",
"status": "True",
"reason": "OperationsCompleted",
"message": "All adapter operations completed successfully",
"last_transition_time": "2025-01-01T10:00:00Z"
}
],
"data": {
"job_name": "validator-job-abc123",
"attempt": 1
},
"created_time": "2025-01-01T10:00:00Z",
"last_report_time": "2025-01-01T10:00:00Z"
}Note: The API automatically sets created_time, last_report_time, and last_transition_time fields.
PATCH /api/hyperfleet/v1/clusters/{cluster_id}
Updates a cluster's spec and/or labels. Only the fields provided in the request body are modified; omitted fields are left unchanged. The generation counter increments when spec is updated.
Request Body:
{
"spec": {
"region": "us-east-1",
"instanceType": "m5.xlarge"
},
"labels": {
"environment": "staging"
}
}Response (200 OK):
JSON response
{
"kind": "Cluster",
"id": "2abc123...",
"href": "/api/hyperfleet/v1/clusters/2abc123...",
"name": "my-cluster",
"generation": 2,
"spec": {
"region": "us-east-1",
"instanceType": "m5.xlarge"
},
"labels": {
"environment": "staging"
},
"created_time": "2025-01-01T00:00:00Z",
"updated_time": "2025-01-01T12:00:00Z",
"created_by": "user@example.com",
"updated_by": "user@example.com",
"status": {
"conditions": [
{
"type": "Reconciled",
"status": "False",
"reason": "ReconciledMissingAdapters",
"message": "Required adapters have not yet reported status",
"observed_generation": 2,
"created_time": "2025-01-01T00:00:00Z",
"last_updated_time": "2025-01-01T12:00:00Z",
"last_transition_time": "2025-01-01T12:00:00Z"
},
{
"type": "LastKnownReconciled",
"status": "True",
"reason": "AllAdaptersReconciled",
"message": "All required adapters report Available=True for the tracked generation",
"observed_generation": 1,
"created_time": "2025-01-01T00:00:00Z",
"last_updated_time": "2025-01-01T00:00:00Z",
"last_transition_time": "2025-01-01T00:00:00Z"
}
]
}
}Note: After a spec update, Reconciled transitions to False until adapters report at the new generation. LastKnownReconciled retains the last known good state.
DELETE /api/hyperfleet/v1/clusters/{cluster_id}
Soft-deletes a cluster. Sets deleted_time and deleted_by, increments generation, and cascades deletion to child nodepools according to the deletion policy: nodepools with required adapters are soft-deleted (their deleted_time and deleted_by are set and generation is incremented, entering Finalizing), while nodepools without required adapters are hard-deleted immediately. The cluster itself enters a Finalizing state — it remains in the database until adapters report Finalized=True, at which point it is hard-deleted automatically.
For more information, please take a look at the delete lifecycle.
Response (202 Accepted):
JSON response
{
"kind": "Cluster",
"id": "2abc123...",
"href": "/api/hyperfleet/v1/clusters/2abc123...",
"name": "my-cluster",
"generation": 3,
"spec": {},
"labels": {},
"created_time": "2025-01-01T00:00:00Z",
"updated_time": "2025-01-01T14:00:00Z",
"created_by": "user@example.com",
"updated_by": "user@example.com",
"deleted_time": "2025-01-01T14:00:00Z",
"deleted_by": "user@example.com",
"status": {
"conditions": [...]
}
}Once a cluster is soft-deleted, creating or updating child nodepools returns 409 Conflict.
POST /api/hyperfleet/v1/clusters/{cluster_id}/force-delete
Permanently removes a cluster that is stuck in the Finalizing state. This bypasses the normal adapter finalization flow — use it only when adapters are unable to report Finalized=True. See delete lifecycle for more details.
The cluster, all its child nodepools, and all associated adapter statuses are hard-deleted immediately. The caller and reason are recorded in an audit log entry before deletion.
The cluster must already be soft-deleted (have a deleted_time). Calling force-delete on an active cluster returns 409 Conflict.
Request Body:
{
"reason": "Adapter crashed and cannot finalize"
}| Field | Type | Required | Constraints |
|---|---|---|---|
reason |
string | Yes | Non-empty, max 1024 |
Response: 204 No Content
GET /api/hyperfleet/v1/nodepools
GET /api/hyperfleet/v1/clusters/{cluster_id}/nodepools
POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools
GET /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}
PATCH /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}
DELETE /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}
POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/force-delete
GET /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses
PUT /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses
POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools
Request Body:
{
"kind": "NodePool",
"name": "worker-pool",
"spec": {},
"labels": {
"role": "worker"
}
}Response (201 Created):
JSON response
{
"kind": "NodePool",
"id": "2def456...",
"href": "/api/hyperfleet/v1/nodepools/2def456...",
"name": "worker-pool",
"owner_references": {
"kind": "Cluster",
"id": "2abc123..."
},
"generation": 1,
"spec": {},
"labels": {
"role": "worker"
},
"created_time": "2025-01-01T00:00:00Z",
"updated_time": "2025-01-01T00:00:00Z",
"created_by": "user@example.com",
"updated_by": "user@example.com",
"status": {
"conditions": [
{
"type": "Reconciled",
"status": "False",
"reason": "ReconciledMissingAdapters",
"message": "Required adapters have not yet reported status",
"observed_generation": 1,
"created_time": "2025-01-01T00:00:00Z",
"last_updated_time": "2025-01-01T00:00:00Z",
"last_transition_time": "2025-01-01T00:00:00Z"
},
{
"type": "LastKnownReconciled",
"status": "False",
"reason": "AdaptersMissingReports",
"message": "Required adapters have not yet reported status",
"observed_generation": 1,
"created_time": "2025-01-01T00:00:00Z",
"last_updated_time": "2025-01-01T00:00:00Z",
"last_transition_time": "2025-01-01T00:00:00Z"
},
]
}
}GET /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}
Response (200 OK):
JSON response
{
"kind": "NodePool",
"id": "2def456...",
"href": "/api/hyperfleet/v1/nodepools/2def456...",
"name": "worker-pool",
"owner_references": {
"kind": "Cluster",
"id": "2abc123..."
},
"generation": 1,
"spec": {},
"labels": {
"role": "worker"
},
"created_time": "2025-01-01T00:00:00Z",
"updated_time": "2025-01-01T00:00:00Z",
"created_by": "user@example.com",
"updated_by": "user@example.com",
"status": {
"conditions": [
{
"type": "Reconciled",
"status": "True",
"reason": "ReconciledAll",
"message": "All required adapters reported Available=True or Finalized=True at the current generation",
"observed_generation": 1
},
{
"type": "LastKnownReconciled",
"status": "True",
"reason": "AllAdaptersReconciled",
"message": "All required adapters report Available=True for the tracked generation",
"observed_generation": 1
},
]
}
}PUT /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses
Same format as cluster status reporting (see above).
PATCH /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}
Updates a nodepool's spec and/or labels. Same semantics as Patch Cluster — only provided fields are modified, and generation increments on spec changes.
Request Body:
{
"spec": {
"machineType": "n2-standard-4",
"replicas": 5
}
}Response (200 OK): Full nodepool resource with incremented generation and updated updated_time/updated_by.
DELETE /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}
Soft-deletes a nodepool. Same lifecycle as Delete Cluster — sets deleted_time and deleted_by, enters the Finalizing state, and is hard-deleted when adapters report Finalized=True.
For more information, please refer to delete lifecycle.
Response (202 Accepted): Full nodepool resource with deleted_time and deleted_by fields set.
POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/force-delete
Same semantics as Force Delete Cluster. The nodepool must already be soft-deleted.
Request Body:
{
"reason": "Adapter unable to finalize nodepool"
}Response: 204 No Content
Resources follow a three-phase delete lifecycle:
Active ──(DELETE)──▶ Finalizing ──(adapters report Finalized=True)──▶ Hard-Deleted
│
└──(POST /force-delete)──▶ Hard-Deleted
- Active — Normal state. Resource is visible in list queries and can be updated.
- Finalizing (soft-deleted) —
DELETEsetsdeleted_timeanddeleted_by, incrementsgeneration. The resource stays in the database so adapters can observe the deletion and clean up external state. Soft-deleted records are excluded from list queries by default. Creating new child resources under a finalizing parent is rejected with409 Conflict. - Hard-Deleted — Permanently removed from the database. This happens automatically when all required adapters report
Finalized=Trueat the current generation. If adapters are stuck,POST .../force-deletebypasses the adapter gating and hard-deletes immediately — but the resource must already be in Finalizing state; calling force-delete on an active resource returns409 Conflict. Repeated force-delete calls after hard-deletion return404 Not Found. Cluster force-delete cascades to all child NodePools and their adapter statuses. NodePool force-delete only removes the NodePool and its adapter statuses.
All list endpoints support pagination:
GET /api/hyperfleet/v1/clusters?page=1&pageSize=10
Parameters:
page- Page number (default: 1)pageSize- Items per page (default: 20)
Response:
{
"kind": "ClusterList",
"page": 1,
"size": 10,
"total": 100,
"items": [...]
}All list endpoints support filtering using TSL (Tree Search Language) query syntax. Example:
curl -G http://localhost:8000/api/hyperfleet/v1/clusters \
--data-urlencode "search=status.conditions.Reconciled='True' and labels.environment='production'"See search.md for complete documentation.
kind- Resource type (Cluster, NodePool)id- Unique resource identifier (auto-generated, RFC4122 UUID v7 format: 36-character lowercase with hyphens)href- Resource URIname- Resource name (user-defined)generation- Spec version counter (incremented on spec updates)spec- Provider-specific configuration (JSONB, validated against OpenAPI schema)labels- Key-value pairs for categorization and searchcreated_time- When resource was created (API-managed)updated_time- When resource was last updated (API-managed)created_by- User who created the resource (email)updated_by- User who last updated the resource (email)
The status object contains synthesized conditions computed from adapter reports:
conditions- Array of resource conditions, including:- Reconciled - Whether all adapters have reconciled at the current spec generation
- LastKnownReconciled - Whether resource is running at any known good configuration
- Additional conditions from adapters (with
observed_generation, timestamps)
In AdapterStatus PUT request (ConditionRequest):
type- Condition type (Available, Applied, Health)status- Condition status (True, False)reason- Machine-readable reason codemessage- Human-readable message
In Cluster/NodePool status (ResourceCondition):
- All above fields plus:
observed_generation- Generation this condition reflectscreated_time- When condition was first created (API-managed)last_updated_time- API-managed. For per-adapter conditions, taken fromAdapterStatus.last_report_time. For aggregated conditions (Reconciled,LastKnownReconciled), computed as the oldest valid adapter report time within the relevant generation bucket — not the latest report timelast_transition_time- When status last changed (API-managed)
All list endpoints accept the following query parameters:
| Parameter | Type | Required | Default | Constraints |
|---|---|---|---|---|
search |
string | No | - | TSL query syntax |
page |
integer (int32) | No | 1 |
- |
pageSize |
integer (int32) | No | 20 |
- |
orderBy |
string | No | created_time |
- |
order |
string | No | - | Must be asc or desc |
| Parameter | Type | Required |
|---|---|---|
cluster_id |
string | Yes |
nodepool_id |
string | Yes |
| Constraint | Value |
|---|---|
| Required | Yes |
| Type | string |
| Min length | 3 |
| Max length | 53 |
| Pattern | ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ |
Must be lowercase alphanumeric, may contain hyphens, and must start and end with an alphanumeric character.
| Constraint | Value |
|---|---|
| Required | Yes |
| Type | string |
| Min length | 3 |
| Max length | 15 |
| Pattern | ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ |
Same naming rules as cluster, but with a shorter maximum length.
| Field | Type | Required |
|---|---|---|
adapter |
string | Yes |
observed_generation |
integer (int32) | Yes |
observed_time |
string (date-time) | Yes |
conditions |
array of ConditionRequest |
Yes |
metadata |
object | No |
data |
object | No |
| Field | Type | Required | Constraints |
|---|---|---|---|
type |
string | Yes | - |
status |
string | Yes | Must be True, False, or Unknown |
reason |
string | No | - |
message |
string | No | - |
- AdapterConditionStatus (used in adapter status reports):
True,False,Unknown - ResourceConditionStatus (used in cluster/nodepool conditions):
True,False - OrderDirection:
asc,desc
When an OpenAPI schema is configured (see deployment.md for setup), the API validates cluster and nodepool spec fields on every create and update request. If no schema is configured, all specs are accepted without validation. When a schema is configured:
POST /clustersandPOST /nodepoolsvalidatespecagainstClusterSpecorNodePoolSpecfrom the schemaPATCH /clusters/{id}andPATCH /nodepools/{id}validate the merged result- Invalid specs return a
400with validation details in the error response
The schema is configured via --server-openapi-schema-path or the validationSchema section in the Helm chart. See Validation Schema for details.
GET /clusters/{id}returns the cluster with aggregated status conditions (Reconciled,LastKnownReconciled, and per-adapter conditions synthesized from adapter reports).GET /clusters/{id}/statusesreturns the raw adapter status records — one per adapter that has reported. These are the individual reports, not the aggregated view.
The same distinction applies to nodepools.
All error responses use the RFC 9457 Problem Details format with content type application/problem+json.
| Field | Type | Always present | Description |
|---|---|---|---|
type |
string | Yes | URI reference identifying the problem type |
title |
string | Yes | Short human-readable summary |
status |
integer | Yes | HTTP status code |
detail |
string | Yes | Human-readable explanation specific to this occurrence |
code |
string | No | Machine-readable error code in HYPERFLEET-CAT-NUM format |
timestamp |
string | No | RFC 3339 timestamp of when the error occurred |
trace_id |
string | No | Distributed trace ID for correlation (from X-Request-Id header) |
instance |
string | No | URI reference for this specific occurrence |
errors |
array | No | Field-level validation errors (see below) |
Error codes follow the HYPERFLEET-CAT-NUM format:
| Category | Meaning |
|---|---|
VAL |
Request validation failures |
AUT |
Authentication errors |
NTF |
Resource not found |
CNF |
Resource conflicts |
LMT |
Rate limiting |
INT |
Internal server errors |
SVC |
Upstream service errors |
JSON response
{
"type": "https://api.hyperfleet.io/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "Request body validation failed",
"code": "HYPERFLEET-VAL-003",
"timestamp": "2025-01-01T12:00:00Z",
"trace_id": "abc123-def456",
"instance": "/api/hyperfleet/v1/clusters",
"errors": [
{
"field": "name",
"message": "name is required"
},
{
"field": "spec",
"message": "spec must not be null",
"constraint": "required"
}
]
}{
"type": "https://api.hyperfleet.io/errors/not-found",
"title": "Not found",
"status": 404,
"detail": "Cluster with id='2abc123...' not found",
"code": "HYPERFLEET-NTF-001",
"timestamp": "2025-01-01T12:00:00Z"
}{
"type": "https://api.hyperfleet.io/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "Cannot create nodepool: parent cluster is being deleted",
"code": "HYPERFLEET-CNF-001",
"timestamp": "2025-01-01T12:00:00Z"
}- Example Usage - Practical examples
- Authentication - API authentication
- Database - Database schema