Skip to content

Using the API

This API belongs to the console for the clusters I run for you. Your own landscape has no portal; there you talk to the garden with kubectl or the CLI. Everything the console does, it does over the same REST API: creating clusters, managing node pools, issuing kubeconfigs, reading billing. This page is the orientation: the shape of the API and the conventions that hold across every endpoint. The API reference then documents each endpoint in full, generated from the live schema.

The API lives under the console host, and the stable surface is versioned with a /v1/ prefix:

https://console.paasbox.com/api/v1/

Requests and responses are JSON over HTTPS. A new major version would move to /v2/ rather than change what /v1/ does.

Two ways in, depending on who is calling:

  • Session: the console authenticates you with a browser session. That is the interactive path; you do nothing special.
  • API key: for scripts, CI, and anything non-interactive. Create a key in the console (see Teams & API keys), then send it in the Authorization header.

Keys look like pbx_live_…, are shown once at creation, belong to a team member, and can be revoked at any time. Send it on every request:

Terminal window
curl -s -H "Authorization: Token $PAASBOX_API_KEY" \
https://console.paasbox.com/api/v1/teams/<team>/clusters/

Keep the key as the durable secret and use it to fetch a fresh kubeconfig per run rather than storing long-lived cluster credentials.

Resources belong to a team, not to a person, so the paths that own resources carry the team slug:

/api/v1/teams/{team_slug}/clusters/
/api/v1/teams/{team_slug}/clusters/{cluster}/node-pools/

Your key acts within the teams its owner is a member of. The Teams & API keys page covers roles and what a key is allowed to do; the activity log attributes every key-driven change back to a person.

List endpoints are paginated 100 items at a time. Ask for a page with ?page=:

Terminal window
curl -s -H "Authorization: Token $PAASBOX_API_KEY" \
"https://console.paasbox.com/api/v1/teams/<team>/clusters/?page=2"

The response wraps the items in a small envelope: count is the total, next and previous are page URLs (or null at the ends), and results holds the items:

{
"count": 137,
"next": "https://console.paasbox.com/api/v1/teams/acme/clusters/?page=3",
"previous": "https://console.paasbox.com/api/v1/teams/acme/clusters/?page=1",
"results": [ /* … */ ]
}

Standard HTTP semantics apply. The ones you will meet:

CodeMeaning
200 / 201 / 204Success: read, created, or deleted (no body).
202Accepted: the work runs asynchronously (e.g. a cluster starting); poll the resource for status.
401 / 403Not authenticated / not allowed. Check the key and the caller’s role in the team.
402A billing or spend limit is blocking the action. Resolve it in the console, then retry.
404No such resource, or it is outside the teams your key can reach.
409Conflict with the current state (e.g. a name already in use).
412 / 422The request was understood but rejected: a precondition failed, or a field did not validate.

Error responses carry a JSON body describing what went wrong; the status code is what decides how to react.

The paasbox CLI, the free path where you run the landscape yourself, talks to two smaller surfaces that do not follow the conventions above. Both are documented here by hand: they are plain endpoints outside the framework the schema is generated from, so they do not appear in the API reference.

Three endpoints under /api/cli/ on the console host implement the OAuth 2.0 Device Authorization Grant (RFC 8628) — the flow gh auth login and docker login use. There is no API key here, because the point is that the caller has none yet; the brakes are the ten-minute expiry, a rate limit per address, and the fact that nothing happens until a signed-in human approves the code. Error bodies are RFC 8628’s, not the shapes above, and each may carry an error_description a client is free to ignore.

POST /api/cli/device/start — open a sign-in. No authentication.

{ "scope": "dns", "client": "paasbox-cli/0.1.0" }
{
"device_code": "…43 url-safe characters…",
"user_code": "FTQK-2X7M",
"verification_uri": "https://console.paasbox.com/device",
"verification_uri_complete": "https://console.paasbox.com/device?code=FTQK-2X7M",
"expires_in": 600,
"interval": 5
}

400 carries invalid_scope or invalid_request; 429 carries slow_down when too many sign-ins start from one address.

POST /api/cli/device/token — polled at interval until the human decides. No authentication; the device code is the credential.

{ "device_code": "" }

While it waits, 400 with one of authorization_pending, slow_down, expired_token, access_denied, or invalid_grant (a device code that is unknown or already spent); 429 with slow_down for too many polls from one address. Polling faster than interval returns slow_down and pushes the gate a further interval out, so a client that ignores it keeps getting it.

Once, and only once, the poll returns 200 with the grant:

{
"token": "",
"team": "Acme GmbH",
"label": "acme",
"endpoint": "https://dns.paasbox.com/v1",
"zone": "paasbox.app",
"base": "paasbox.app",
"scopes": ["dns"]
}

Any later poll on the same device code gets invalid_grant. The token is created at collection, not at approval, so it does not exist anywhere in between. endpoint, zone and base come from the answering portal’s own configuration rather than a literal, so read them from the response rather than hard-coding them.

GET /api/cli/whoami — what a stored token is, for paasbox auth status. Send the token as a bearer credential:

Terminal window
curl -s -H "Authorization: Bearer $DNS_TOKEN" \
https://console.paasbox.com/api/cli/whoami
{
"team": "Acme GmbH",
"label": "acme",
"scopes": ["dns"],
"created": "2026-09-07T10:12:31+00:00",
"endpoint": "https://dns.paasbox.com/v1",
"zone": "paasbox.app",
"base": "paasbox.app"
}

401 with invalid_token means the token is unknown or was revoked — the CLI reads that as “signed out”, not as a transient failure.

The free hosted names under <team>.paasbox.app are written through a Hetzner-Cloud-compatible zone and rrset API on its own host:

https://dns.paasbox.com/v1

It reproduces the subset of the Hetzner Cloud API that Gardener’s DNS extension, the bring-up and the hcloud CLI actually call, request for request, and adds the one thing Hetzner cannot: a bearer token scoped to a team. So an unmodified client works against it — point it at the endpoint and give it the team token instead of a Hetzner token:

Terminal window
export HCLOUD_ENDPOINT=https://dns.paasbox.com/v1
export HCLOUD_TOKEN=$DNS_TOKEN
hcloud zone rrset set-records --record 203.0.113.7 paasbox.app api.garden.acme A
EndpointWhat it does
GET /v1/zoneslist, filtered by ?name=; returns the one zone your token can see
GET /v1/zones/{zone}by id or by name
GET /v1/zones/{zone}/rrsetsyour team’s record sets only
POST /v1/zones/{zone}/rrsetscreate
GET / DELETE /v1/zones/{zone}/rrsets/{name}/{type}read or remove one
POST …/rrsets/{name}/{type}/actions/{action}set_records, add_records, remove_records, change_ttl
GET /v1/actions?id= and GET /v1/actions/{id}action polling, the way Hetzner’s Go client does it

Request and response bodies are Hetzner’s, unchanged, down to the meta.pagination envelope and the {"error": {"code": …, "message": …}} shape. Where the upstream refuses something, its answer and its status code are passed through verbatim.

What is different is what the token may do:

  • Writes are allowed to your team’s own name and anything under it. The zone apex, every other team’s names and all of ours are refused with 403 forbidden.
  • Reads are filtered. A list shows only your team’s record sets; a read of any other name is 404 not_found, not a 403, so the zone cannot be enumerated through this door.
  • Types are limited to A, AAAA, CNAME and TXT. NS is refused anywhere.
  • Actions are filtered to zone actions, so the sequential action ids of a Hetzner project reveal nothing else.
  • Quotas (as of 2026-09-07): 200 records and five active tokens per team.
  • 401 unauthorized without a bearer token, 502 upstream_unavailable if Hetzner cannot be reached.

Every write is recorded — team, token, name, type, action, time and the status the upstream returned — including the ones that were refused.

The API reference lists every endpoint, parameter, request body, and response shape. It is generated from the running service’s schema, so it tracks the API as it ships. The two CLI surfaces above are outside that schema and are documented on this page instead.