Skip to content

Overview

The Aether HTTP API for managing organizations, Kubernetes clusters, node pools, container registries, and billing on the Aether platform.

Base URL

All endpoints are served under https://api.aetherplatform.cloud. Versioned routes live under the /api/v1 prefix.

Authentication

Requests authenticate with an API token of the form aether_<hex>, sent in the Authorization header as a Bearer credential:

Authorization: Bearer aether_0123456789abcdef...

Tokens are created per organization by an owner signed in to the portal (see the API Tokens endpoints, which refuse API-token callers) and the raw secret is shown only once, at creation time. Unauthenticated requests receive 401.

The portal calls the API with the signed-in user’s Keycloak access token. Such a token is accepted only when it is signed by a key the realm publishes (matched by kid), unexpired, issued by the platform’s realm (iss), and issued to one of the platform’s own clients (azp); anything else is 401. A user token whose e-mail address is not verified (email_verified is not true) is refused with 403 and error.code email_unverified on GET /api/v1/organizations and on every operation under /api/v1/organizations/{orgID}; creating an organization (POST /api/v1/organizations) is the one organization operation it may use. API tokens are not affected by any of these checks.

The passkey (WebAuthn) routes under /api/v1/webauthn serve the portal’s own sign-in and passkey settings and are not described operation by operation in this document. Registering a passkey (POST /api/v1/webauthn/register/begin and …/register/finish) needs a verified e-mail address as well: an unverified one is refused with 403 and error.code email_unverified. Signing in with a passkey (POST /api/v1/webauthn/login/begin and …/login/finish) does not depend on it.

Authorization

Every operation under /api/v1/organizations/{orgID} is checked against the caller’s role in that organization. Roles are ordered viewer < member < admin < owner; an operation names a minimum role, and an operation without an explicit minimum requires admin.

Minimum roleOperations
viewerEvery GET, except those listed below; the SSE token mints; POST …/clusters/{clusterID}/metrics (a read); support tickets (create, update, messages, attachments)
memberCreate, update and delete clusters and node pools; PUT …/network; POST …/upgrade; GET …/kubeconfig and …/proxy-kubeconfig; GET …/webhooks/{webhookID}/deliveries; POST …/registry/repositories/{repoName}/tags/{tag}/scan
ownerPOST …/api-tokens (create an API token)
adminEverything else: organization update, member invite and removal, listing, reading and revoking API tokens, webhook writes (incl. rotate-secret and test), budget writes, the audit log, every registry write (enable, disable, repository delete, robot accounts, robot rotate, cleanup policies incl. dry-run and run), payment methods, setup-intent

Some operations are stricter than this table and say so in their own description: deleting the organization, changing a member’s role or payment_mode, paying an invoice (including its confirm step) and discounts (owner or admin login, API tokens refused). A portal user inviting a member cannot grant a role above their own, so only an owner can add an owner; an organization API token with role admin may grant any role, owner included (this is how ownership is transferred with an API token — which only an owner can create). A member or viewer token cannot add or remove members at all: those operations require admin and refuse it with 403 insufficient_role. Removing an owner member requires an owner login or an organization API token with role admin; changing an owner’s role or granting owner through the member update requires an owner login. Removing or demoting the organization’s last owner is refused for every caller with 409 last_owner.

API tokens. In the role policy an organization API token acts with its own stored role — viewer, member or admin, chosen when it is created; admin is the default and behaves exactly as every token did before token roles existed — except on the API Tokens operations, which refuse every API token (token_not_permitted) — a token cannot create, list or revoke tokens. The stricter operations above also refuse every API token: deleting the organization, changing a member’s role or payment_mode, paying an invoice and discounts. A machine token (minted by the platform for a cluster’s cloud controller or a proxy kubeconfig) never acts as an organization member: it is refused on every operation except the load-balancer operations a cloud-controller token was minted for.

⚠ An organization API token with role admin acts with OWNER-EQUIVALENT power over membership — it can add an owner and remove one — and no API token is bound to the member who created it: removing or demoting that member leaves the token working. After removing or demoting an owner, revoke the API tokens they created. The API Tokens list is the primary record: each token records created_by_member_id and created_by_email, and the email is kept after the member is removed, so there is nothing to inspect before removing the member. Audit events (api_token.create) are supplementary; audit writes are best-effort and tokens created by another token before this policy show actor_type api_token.

Refusals are 403 with error.code forbidden (not a member), insufficient_role or token_not_permitted.

Rate limiting

Requests are rate-limited to 100 requests per minute per key and endpoint (token bucket, burst 10). Exceeding the limit returns 429 with error.code rate_limited and a Retry-After header.

The endpoint is the HTTP method plus the route: requests to one route with different ids or names in the path (other clusters, repositories, tags) share one budget, the query string does not count, and GET and PUT on the same path are separate endpoints. A client calling several routes therefore has a full budget on each of them.

The key is the credential when the request carries one that is valid: each signed-in user and each API token has its own bucket per endpoint, whatever address it is used from. Requests without a credential, or with one that is not valid, are keyed on the client address. That address is the client’s address as forwarded by the platform’s proxy only where the deployment trusts that proxy hop; otherwise it is the address of the proxy itself, so all such clients behind it share one budget. X-Forwarded-For entries added by the client, X-Real-IP and True-Client-IP are never used, and the source port is never part of the key. Many concurrent calls with one API token to one endpoint therefore share one budget — retry a 429 after Retry-After. A token that is over its budget is refused before it is checked against the token store.

Unauthenticated and API-token traffic is also bounded by a per-address ceiling of 1000 requests per minute (burst 100) across all endpoints: every request from a client address is charged to it unless it carries a valid signed-in user token, which is bounded only by that user’s own per-endpoint buckets. Once an address has used its ceiling up, every request from it without a valid user token — including API-token requests — is refused (429, same response) until it refills. Live event streams (Server-Sent Events opened with ?sse_token=) carry no bearer token, so they count as unauthenticated: where the platform’s proxy hop is not trusted, the event streams of all signed-in users relayed through one proxy share that proxy’s ceiling and per-endpoint budget.

One more exception affects API tokens: checking a presented API token is charged to the client address’s budget on that endpoint if the token turns out not to be valid, so a request with an API token is refused (429) while its source address has no budget left on that endpoint — for example when unauthenticated or invalid requests from the same address have used it up. Such a request is refused without its token being checked, and is accepted again once the bucket refills. An API-token request that is still being checked also holds part of its source address’s budget on that endpoint until the check completes, so more than about 10 (the burst) concurrent API-token requests to one endpoint from one address — for example several Terraform runs behind one NAT — can receive 429 even with valid tokens; retry after Retry-After (the Terraform provider retries 429 automatically). Portal user tokens are not subject to this.

The passkey sign-in routes (POST /api/v1/webauthn/login/begin and …/login/finish) have a stricter budget of 10 requests per minute per client address (burst 5), on top of the limit above. It is keyed on the address only: a credential sent with the request does not change it.

Pagination

List endpoints use opaque cursor pagination via query parameters:

?cursor=<UUID>&limit=<N>

limit defaults to 50 and is capped at 100. The response envelope carries the items plus a next_cursor field; when next_cursor is absent there are no further pages. Pass the returned next_cursor value back as cursor to fetch the next page.

Errors

Errors are returned as a JSON object with an error member containing a stable code, a human-readable message, and (for validation failures) a details array. A 5xx answer written by the API’s error writer additionally carries status_url, the address of the platform status page (https://status.aetherplatform.cloud), so a client can point its user at the place where an incident is reported. A 5xx produced elsewhere (a recovered panic) does not carry it; treat the member as optional. Error messages never echo database or upstream errors: a failed registry store call is 500 with error.code store_error and a fixed message.

Packages

A cluster is billed either from a package or per resource (“custom”). The choice is made when the cluster is created — the plan field on the create request — and is fixed for the cluster’s lifetime. The catalogue currently offers four packages (dev, starter, growth, scale), and a retired package can still appear on an existing cluster; each is a flat monthly fee per cluster covering an allowance of vCPUs, memory and node disk, and anything above the allowance is charged per resource, each dimension on its own. Omitting plan bills the cluster entirely per resource. Either way load balancers, persistent volumes, registry storage and the public API endpoint — which every cluster is required to have, and which no package fee includes — are charged separately. The current package prices and allowances are shown in the portal when you create a cluster (and, once published, on the pricing page).

Information

  • OpenAPI version: 3.1.0

An Aether API token of the form aether_<hex>, sent as Authorization: Bearer <token>.

Security scheme type: http

The Keycloak access token of a user signed in to the portal, sent as Authorization: Bearer <token>. Accepted only when issued by the platform’s realm to one of the platform’s clients (see Authentication). Operations that refuse API tokens accept only this credential; an API token there is answered 403 token_not_permitted (organization operations) or 403 forbidden (/api/v1/admin).

Security scheme type: http

Bearer format: JWT