Skip to main content
You can provision and manage GPU capacity in Ornn-managed Kubernetes entirely through APIs. A platform team needs only an Ornn API key and an HTTPS client. The console and the Ornn CLI are optional. This guide walks through the full lifecycle: find reserved hosts, create a cluster, wait for it, fetch its kubeconfig, run a GPU Job, scale workers, and tear the cluster down. Every step shows the Ornn CLI, curl, and Python.

Two APIs, two jobs

The Ornn API manages the cluster and its capacity. The standard Kubernetes API runs your workloads on that capacity. Use any Kubernetes client library or kubectl. Never send the Ornn API key to the Kubernetes API server, or the kubeconfig token to api.ornn.com. The API reference documents every Ornn operation used here. Download the OpenAPI contract to generate a client.

What the API does not do

  • No gRPC or GraphQL. The Ornn API is JSON over HTTPS.
  • No purchasing. A cluster runs on hosts your organization already reserved. Reserve capacity before you automate launches.
  • No automatic scaling of Ornn capacity. Ornn does not provide a Cluster Autoscaler or Karpenter integration, and pending Pods do not add workers. To change capacity, your automation calls the add and remove worker endpoints.
  • One cluster at a time. Your organization can run one Kubernetes cluster, and cannot run a Kubernetes cluster and a Slurm cluster at the same time. A cluster counts until its teardown finishes; a failed launch does not count. Grow the cluster you have by adding workers.
  • No credential renewal. The kubeconfig token is issued for seven days. Reading access again returns the same token. Contact Ornn support for new credentials.
  • No external clusters. You cannot attach Ornn workers to an existing EKS, GKE, or AKS cluster.
  • No single-cluster read. There is no GET for one cluster. Read the cluster list and select by ID.

Before you start

  • Belong to an approved organization and create an API key in Account Settings → Developer.
  • Hold an active reservation with any required payment complete. Its hosts must be online.
  • Save data on the hosts you plan to use. Ornn prepares each host before it joins a cluster: running processes are stopped, the tenant home directory is wiped, and any VM on the host is removed.
  • For Python, install requests and the Kubernetes Python client: pip install requests kubernetes. For shell, install curl and jq.
  • If you choose a Private cluster, the machine that calls the Kubernetes API needs an active WireGuard connection to your organization’s network. See Private access with WireGuard.
The Ornn CLI signs in with ornn login and does not accept API keys. In scripts, pass --format json, --yes, and a stable --idempotency-key to CLI commands that change resources. See Human and automation modes. Set your environment once:
The Python examples build on one another. Start with this helper, which raises an error carrying the HTTP status and detail.code, and retries only requests that are safe to retry:
A 429 is returned before your request reaches Ornn’s services, so any method is safe to retry after Retry-After. Do not blindly retry a write after a timeout or 5xx; read the current state first, as each step below describes.

1. Find a reservation and eligible hosts

List your active reservations, then list the hosts on the one you choose. A host can join as a worker when all of these are true:
  • status is bare-metal.
  • controller_id is null, so it is not in another cluster.
  • health_readiness.ready is true.
health_readiness.reasons containing heartbeat:stale means the host has not reported recently, and launch refuses it. heartbeat:unknown in advisory_reasons means Ornn has no heartbeat evidence either way; it does not block launch, but it does not prove the host is reachable. Node count means hosts, not GPUs.
The CLI’s automation form needs your organization ID; ornn context --format json returns it, and so does GET /v1/auth/session with your API key.

2. Create the cluster

Creating a cluster prepares every selected host, which wipes its tenant home directory and removes any VM on it. This request starts provisioning; it is not a dry run.
Send kind, reservation_id, and the exact node_ids to use. Listing hosts explicitly is the safe choice: if you omit both node_ids and virtual_machine_ids, Ornn uses every bare-metal host on the reservation. Set network_mode to private, and optionally network_id, for a cluster reachable only over WireGuard.
Expect 202 Accepted with the new cluster’s id, state: provisioning, and its namespace. That response means the launch was accepted, not that anything is ready; node_count and gpu_count are usually 0 until workers attach. Create is safe to repeat for the same reservation, but it is not a scaling operation:
  • If the reservation’s cluster is active, or provisioning with workers attached, the request returns that cluster unchanged with 202, even if node_ids differs.
  • If the last launch on the reservation failed, the request relaunches it under the same id.
  • Ornn does not read an Idempotency-Key header on this route. After a timeout or 5xx, list your Kubernetes clusters before sending the request again, as the Python example does. Your organization runs at most one at a time, so a live cluster in that list is the one your earlier request created.

3. Wait for the cluster to become active

Poll the cluster list every 10 seconds and select your cluster by controller.id. Read three things:
  • controller.state: pending or provisioning while launching, active when every launch worker has joined and access has been issued, failed when the launch could not finish.
  • controller.progress.stages: launch stages create_plane, prepare_hosts, join_workers, apply_plane, and issue_access, each pending, running, completed, or failed. A failed stage’s detail carries the reason.
  • nodes[].state for each worker: joining, then ready; join_failed means that host could not join.
Creating the control plane, preparing hosts, and pulling images take several minutes. A launch whose workers cannot join is marked failed after about 30 minutes, so a client deadline of an hour leaves room for the rest. If your deadline passes while the cluster is still provisioning, contact Ornn support with the cluster ID; do not launch again. If a worker stays joining, check its host’s health_readiness in the host list from step 1. heartbeat:stale means the host went offline.
active means Ornn finished its part. It does not prove each Kubernetes Node is Ready or advertises GPUs; step 5 checks that through the Kubernetes API.

4. Fetch the kubeconfig

Request the cluster’s access. The response contains kubeconfig, the API server endpoint, and namespace, the namespace Ornn configured for your workloads. A 404 with kubernetes_credentials_not_ready means access has not been issued yet; keep polling step 3. Any other 404 means the cluster ID is wrong or not visible to your organization. The kubeconfig’s token has cluster-admin rights and is valid for seven days. Store it like a password, with owner-only file permissions.
The CLI writes the file with 0600 permissions and refuses to overwrite an existing file unless you add --overwrite.

5. Run a GPU smoke Job through the Kubernetes API

From here on you talk to the cluster’s own API server with the kubeconfig. Run GPU work in the namespace from step 4: in that namespace, Ornn’s scheduling rules place Pods on your reserved workers, and a ResourceQuota named ornn-gpu-quota caps the GPUs its Pods can request. Namespaces you create yourself do not receive those scheduling rules. This NVIDIA example checks the Nodes, requests one GPU, runs nvidia-smi, and waits up to five minutes. The Node list also shows the managed control-plane Nodes, which are not GPU capacity. For AMD capacity, use an image with ROCm, request amd.com/gpu, and run amd-smi list.
Success means each worker Node is Ready with GPUs allocatable, the Job completes, and its log lists an NVIDIA GPU. A failed or timed-out Job stays for inspection; check its Pods and events before deleting it. Deleting the Job removes only the test, not the cluster or reservation. For workload guidance such as storage, MPI, images, and queueing, see Run your application.

6. Scale workers up or down

Scaling means adding or removing whole hosts through the Ornn API. Kubernetes autoscalers change Pod replicas, not Ornn hosts.

Add a worker

Choose an eligible host as in step 1, preferably from the reservation the cluster launched on, and add it. A new host normally returns state: cleaning while Ornn prepares it. It then appears in the cluster’s nodes as joining, then ready. Repeating the request is safe: it returns cleaning until preparation finishes, then the member’s current state.
Adding a worker does not raise ornn-gpu-quota, which Ornn sizes from the cluster’s workers when it first becomes active. A host from a different reservation is labeled with its own reservation, while Pods in your namespace are pinned to the reservation the cluster launched on, so they do not schedule there. After the worker is ready, confirm in Kubernetes that its Node is Ready with GPUs and that the quota covers the GPUs you plan to request. Contact Ornn support if it does not.
Add worker errors include 404 controller_not_found when the cluster is failed or being torn down, 404 node_not_found when the host is not yours, and 409 cluster_nodes_not_available when the host is not in bare-metal status. A 409 with an empty JSON object means the host already belongs to another cluster.

Remove a worker

Removing a worker stops the workloads on it and can lose its local data. Checkpoint first. Removing a worker does not cancel its reservation or stop its billing.
Optionally drain the Node in Kubernetes first so Pods move off it gracefully. Then remove it. The response is 200 with state: draining, which means cleanup was sent. The worker leaves the cluster’s nodes once its host confirms cleanup. cleanup_failed means the host reported that cleanup did not finish; contact Ornn before reusing it. Repeating the request while the worker drains sends cleanup again; after it has left, the request returns 404 node_not_found.

Drive scaling from your own controller

Because Ornn does not watch your Pods, a scaling loop lives in your automation. A typical loop reads pending GPU Pods and Node capacity from the Kubernetes API, decides how many hosts it needs, and calls the add or remove worker endpoint for one host at a time, waiting for ready or removal before the next change. Scaling is bounded by the hosts your organization has reserved; it never buys capacity.

7. Tear down the cluster and confirm

Teardown deletes the cluster’s workloads and revokes its kubeconfig. Export data first. Reservation billing continues, and separately provisioned shared filesystems are not deleted.
The teardown response is 202 with state: deleting. Ornn deletes the cluster’s namespace and workloads, then waits up to five minutes for each worker to confirm cleanup before removing the cluster. Hosts that never confirm are released anyway and cleaned when they reconnect. Repeating the request during teardown returns {"state": "deleting"} and does not restart cleanup. Teardown is complete when both of these are true:
  • The cluster no longer appears in the cluster list.
  • GET /v1/orchestrator/controllers/{cluster_id}/access returns 404 with cluster_not_found.
After that, teardown itself returns 404 with no body. If the state becomes failed instead, send the teardown again, and contact Ornn support if it fails twice. Wait for teardown to finish before you create another cluster.
After teardown, the hosts return to your reservation. Delete your local kubeconfig; a new cluster issues new credentials.

Retries and idempotency

The /v1/orchestrator routes share a limit of 240 requests per minute for each user. Every API key you create counts against your limit, and so does your console session. Polling every 10 seconds uses a small fraction of it. A 429 includes Retry-After. Cluster operations return errors as {"detail": {"code": "...", "message": "..."}}; show detail.message to people and branch on detail.code. Authentication and organization-approval errors use {"detail": "...", "code": "..."}. Malformed JSON, unknown request fields, or a missing Content-Type return 400, 422, or 415 with a plain-text body. See errors and safe retries.

Kubernetes access

Console launch, networking, storage, and running applications on your cluster.

Cluster API reference

Request and response schemas for every cluster and worker operation.