Aether platform status
Overview
Aether API 1.0.0
Section titled “Aether API 1.0.0”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 role | Operations |
|---|---|
viewer | Every GET, except those listed below; the SSE token mints; POST …/clusters/{clusterID}/metrics (a read); support tickets (create, update, messages, attachments) |
member | Create, 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 |
owner | POST …/api-tokens (create an API token) |
admin | Everything 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).
Authentication
Section titled “Authentication”bearerApiToken
Section titled “bearerApiToken”An Aether API token of the form aether_<hex>, sent as Authorization: Bearer <token>.
Security scheme type: http
bearerPortalJwt
Section titled “bearerPortalJwt”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