> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ornn.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrate with the Ornn REST API

> Authenticate, follow shared request conventions, and handle errors, pagination, rate limits, and retries.

Call `https://api.ornn.com` from your application using an [organization API key](/api-keys). You do not need to install or sign in with the Ornn CLI.

This reference covers every operation an API key can perform: authentication, reservation and node discovery, VM and Bare Metal access, Kubernetes and Slurm clusters, SSH keys, storage volumes and GitHub repositories, private networks, monitoring, buying and selling capacity (Reserve, Market, Spot, and Relist), and billing. Each endpoint includes request parameters, response schemas, and examples in multiple languages. Examples use fictional IDs and credentials; replace them with your own values.

> **[Download OpenAPI](/openapi.yaml)**
>
> Import the OpenAPI 3.1 contract into Postman, Swagger tools, or a compatible client generator.

> **[Create an API key](/api-keys)**
>
> Create, use, rotate, and revoke keys, and see what a key can and cannot do.

> **[Kubernetes integration](/guides/kubernetes-access#provision-and-manage-through-rest)**
>
> Create a cluster, retrieve access, run a GPU Job through the Python client, and manage workers.

> **[CLI and REST equivalents](/cli#cli-and-rest-equivalents)**
>
> Translate each `ornn` command into the REST request that does the same job.

You can also save the contract locally:

```bash theme={null}
curl --fail --silent --show-error --location \
  https://docs.ornn.com/openapi.yaml --output ornn-openapi.yaml
```

The contract's version identifies this document revision. Use the server and paths in the specification exactly, including `/commerce` and `/v1`; there is no additional shared path prefix.

## Authenticate

Create a key in **Account Settings → Developer**, then supply it as a bearer token:

```bash theme={null}
export ORNN_API_KEY="ornn_sk_example..."

curl --fail-with-body --silent --show-error \
  https://api.ornn.com/v1/auth/session \
  -H "Authorization: Bearer $ORNN_API_KEY"
```

The response's `organizations` array contains exactly the key's organization. You do not need an organization header. A key does not bypass organization approval, reservation ownership, payment, or node eligibility requirements. It cannot manage accounts, teams, or API keys; those routes return `403` with `api_key_requires_session`.

Keep keys in your server's secret store; do not embed them in a browser application or a public repository. Browsers on other sites cannot call `api.ornn.com` directly, so call the API from server code, a job runner, or CI.

## Choose the API for the operation

| Operation | Interface and credentials |
| - | - |
| Inspect reservations and GPU nodes | Ornn REST API with an Ornn API key |
| Create an Ornn-managed cluster, add or remove workers, or tear it down | Ornn REST API with an Ornn API key |
| Register SSH keys and choose which keys a reservation uses | Ornn REST API with an Ornn API key |
| Create, scale, or delete GPU workloads inside a Kubernetes cluster | That cluster's standard Kubernetes HTTP API with its cluster credentials |
| Create API keys, manage your profile, team, or notifications | The Ornn console, signed in |

Ornn's documented customer provisioning interface is REST over HTTPS. This contract does not define a GraphQL endpoint or a public gRPC service. You can use [Kubernetes client libraries](https://kubernetes.io/docs/reference/using-api/client-libraries/) to manage workloads without shelling out to a command-line tool. If your integration requires a specific gRPC protocol, such as the Kubernetes Cluster Autoscaler's [external gRPC provider](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/cloudprovider/externalgrpc), share the client and protocol with Ornn before building against it.

The cluster endpoints create Ornn-managed clusters on capacity your organization already holds. They do not purchase GPUs, attach workers to an existing EKS/GKE cluster, or automatically increase purchased capacity when pods are pending.

## Integration sequence

1. [Check authentication](/api-reference/session) and confirm the returned organization and its `approved` flag.
2. [List reservations](/api-reference/reservations/list) and [inspect their nodes](/api-reference/nodes/list). Confirm eligible capacity before launch; see the [cluster prerequisites](/guides/kubernetes-access#before-you-start).
3. For Slurm, VM, or Bare Metal access, [add an organization SSH key](/api-reference/ssh-keys/create). Kubernetes API access does not need one.
4. [Create a cluster](/api-reference/clusters/create) with explicit worker IDs, then [poll its state](/api-reference/clusters/list).
5. [Retrieve cluster access](/api-reference/clusters/access). Connect a Kubernetes client using the supplied server, credentials, and namespace; verify worker readiness and run a GPU workload through the cluster's Kubernetes API, as the [Kubernetes REST walkthrough](/guides/kubernetes-access#provision-and-manage-through-rest) shows.
6. [Add](/api-reference/clusters/add-node) or [remove](/api-reference/clusters/remove-node) existing workers as needed. [Tear down](/api-reference/clusters/teardown) the cluster when finished; teardown does not cancel the capacity reservation.

## Request conventions

| Convention | Rule |
| - | - |
| Base URL | `https://api.ornn.com`, plus the full path from the contract |
| Credentials | `Authorization: Bearer ornn_sk_...` on every request |
| Bodies | JSON with `Content-Type: application/json`. The gateway rejects request bodies over 32 MiB; large files use staged uploads. |
| Field names | Paths under `/commerce` and `/v1/markets` use camelCase, such as `reservationId`. Paths under `/v1/orchestrator`, `/v1/organizations`, and `/v1/auth` use snake\_case, such as `reservation_id`. |
| IDs | UUID strings. A reservation ID, node ID, VM ID, and cluster ID are different values; pass the one each path names. |
| Timestamps | RFC 3339. Responses use UTC, such as `2026-10-14T12:00:00Z`. Send an explicit offset or `Z`. |
| Prices | Integer micro-USD per GPU-hour, so `2500000` is \$2.50. Amounts such as down payments are integer cents. |
| New fields | Responses can gain fields. Ignore fields you don't use. Some request bodies reject unknown fields, so send only documented fields. |

## Handle asynchronous operations

A `202 Accepted` response means the request was accepted, not that the cluster or worker is ready. Save the returned ID and [poll cluster state](/api-reference/clusters/list). Creation returns the cluster ID as `id`; worker changes return `controller_id` and `node_id` or `virtual_machine_id`.

The paths use `controllers` for cluster resources. Use the returned cluster `id` in those paths, not a reservation ID. Reservation discovery uses `reservationId`; cluster requests use `reservation_id`.

SSH-key changes are also asynchronous. A successful assignment or push means delivery is queued. Wait until the key's fingerprint appears in `authorized_key_fingerprints` on the [reservation's nodes](/api-reference/nodes/list) before you connect.

Poll every five seconds with a deadline. Stop on a terminal failure and preserve the response for diagnosis. A timeout is an unknown outcome: inspect current state before repeating a mutation.

## Pagination

List endpoints return one page at a time and an opaque cursor for the next page. Pass the cursor back unchanged, keep the same filters, and stop when it is absent, null, or empty. Don't build or edit cursors.

| Endpoint family | Page size | Cursor field | Next-page parameter |
| - | - | - | - |
| [Reservations](/api-reference/reservations/list) | `limit`, 1–100 | `nextCursor` | `cursor` |
| [Organization SSH keys](/api-reference/ssh-keys/list) | `limit`, 1–500 (default 25) | `next_cursor` | `cursor` |
| [Clusters](/api-reference/clusters/list) | Newest 500, no paging | None | None |
| [Reservation nodes](/api-reference/nodes/list) | Up to 5,000 per reservation | None | None |

Cluster lists have no cursor or lookup by ID. A missing entry does not prove deletion or completed teardown. Stop and contact Ornn if the cluster you are tracking is absent unexpectedly.

## Errors

Check the HTTP status first, then read the body. Error bodies come in these shapes:

```json Customer error theme={null}
{
  "detail": {
    "code": "ssh_key_not_owned",
    "message": "That SSH key belongs to another account."
  }
}
```

```json Access denied theme={null}
{
  "detail": "Your organization is still being reviewed and cannot use this yet.",
  "code": "not_approved"
}
```

```json Rate limited theme={null}
{
  "error": "rate_limited",
  "retry_after": 42,
  "detail": "Too many requests. Try again in 42 seconds."
}
```

* **Customer errors** carry a stable `detail.code` for your logic and a `detail.message` you can show to people.
* **Access denials** carry a top-level `code`: `unauthenticated` (`401`), `not_member` or `not_approved` (`403`), and `session_contract_unavailable` (`503`) when a route is not available to API callers. Don't retry `session_contract_unavailable`.
* **Rate limits** carry `retry_after` and a `Retry-After` header.
* Some routes return only a string `detail`, an `error` string, or a body that isn't JSON. Tolerate all of these.

| Status | Handling |
| - | - |
| `400` / `422` | Correct the request parameters or JSON body. |
| `401` | Check the key, whether it was revoked, and the creator's organization membership. |
| `403` | Check organization approval and access to the operation. `api_key_requires_session` means the operation needs the console. |
| `404` | The resource is absent or not visible to this organization. During launch, the access endpoint can also report credentials not ready. |
| `409` | Inspect the conflict before retrying; a node may already be assigned or require a different launch selection. |
| `429` | Wait for `Retry-After`, or at least 60 seconds when it is absent, then retry with jitter. |
| `5xx` | Back off on reads; inspect state before retrying a mutation. |

Do not log API keys or cluster credentials when collecting errors.

## Rate limits

Ornn limits requests per user and per source IP address in one-minute windows. An API key counts toward its creator's user budget, shared with that person's other keys and console sessions. These are the current budgets; they can change, so rely on `429` responses rather than hard-coding them.

| Requests | Per user | Per IP address |
| - | - | - |
| `/v1/orchestrator/...` | 240 | 240 |
| `/v1/observability/...` | 300 | 1,000 |
| `POST`, `PUT`, `PATCH`, `DELETE` under `/commerce` | 120 | 120 |
| `GET /v1/auth/session` | 40 | 300 |
| Anything else | None | 600 |
| Requests with a rejected API key | None | 30 |

Exceeding the per-IP write limit under `/commerce` blocks that address for those writes for five minutes. Exceeding other limits returns `429` until the window resets. A `429` from the per-IP limits may not include `Retry-After`.

Spread polling across your fleet, poll every five seconds rather than continuously, and cache reads such as reservation lists between runs.

## Retries and idempotency

Retry reads (`GET`) with exponential backoff and jitter. Before retrying a write that timed out, read current state; the first attempt may have succeeded.

Some writes accept an `Idempotency-Key` header that makes a retry safe. Generate a new UUID for each logical operation and reuse it only when retrying that exact request. Keys are scoped to your organization and kept for 24 hours.

| Routes | `Idempotency-Key` | Retry behavior |
| - | - | - |
| Writes under `/commerce` | Send on every write; routes that require it return `400` without it | The same key and request returns the original successful response. A different request returns `409`. |
| `POST /v1/markets/spot/orders`, `POST /v1/markets/schedules/purchases`, `POST /v1/markets/schedules/{listingId}/offers` | Required, 1–200 characters | The same key and body returns the original order, purchase, or offer. A different body returns `409` `idempotency_conflict`. |
| Writes under `/v1/orchestrator` and `/v1/organizations` | Not supported, except volume file uploads | Read state before retrying. For example, list clusters before repeating a cluster create. |

A `409` on a retry with the same key can also mean the first attempt is still running. Wait, then retry with the same key.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.