REST-API
Mit der REST-API kann ein Agent oder ein Skript das tun, was die Seiten von PaaSbox Clusters im Portal tun, mit einem API-Schlüssel des Teams statt eines Browsers. Diese Seite listet jeden Endpunkt mit Scope, Body, Antwort und Fehlern, und die Regeln, die für alle gelten.
Basis-URL und Anmeldung
Abschnitt betitelt „Basis-URL und Anmeldung“| Punkt | Wert |
|---|---|
| Basispfad | /api/v1/teams/{team}/k3s/ auf dem Host des Portals. Die Seite API keys & agents zeigt die vollständige URL für dein Team. |
{team} | Der Slug des Teams. Er muss das eigene Team des Schlüssels sein; jedes andere antwortet 404 not_found. |
{cluster} | Der Slug des Clusters (sein DNS-Label) oder seine ID (eine UUID). Ein Slug findet einen Cluster, der nicht gelöscht ist; eine ID findet auch einen gelöschten. |
| Header | Authorization: Bearer <key>. Authorization: Api-Key <key> geht auch. |
| Schlüssel | <prefix>.<secret>, angelegt von einem Team-Admin auf API keys & agents (Einem Agenten Zugriff geben). Das Präfix benennt den Schlüssel im Portal und im Audit-Log. |
| Browser-Sitzung | Eine angemeldete Sitzung funktioniert auf den REST-Endpunkten ebenfalls, mit den eigenen Rechten der Person und allen vier Scopes. |
| Bedingungen bei jedem Aufruf | Der Schlüssel ist weder widerrufen noch abgelaufen, seine Besitzerin oder sein Besitzer ist noch aktives Mitglied des Teams, und das Team hat PaaSbox Clusters. |
| Format | JSON, Felder in camelCase, Zeiten als RFC-3339-Strings in UTC. |
Scopes und Rollen
Abschnitt betitelt „Scopes und Rollen“Jeder Endpunkt braucht genau einen Scope. Ein Schlüssel hat die Scopes, die beim Anlegen angehakt wurden.
| Scope | Erlaubt |
|---|---|
k3s:read | Cluster, ihre Gesundheit, Nodes, Snapshots, Operationen, Upgrades und Add-ons sehen, dazu die Hetzner-Projekte und Servertypen, auf denen ein Cluster entstehen kann. |
k3s:write | Snapshots machen, Upgrades einplanen, Add-ons an- und ausschalten oder einstellen. |
k3s:access | Temporäre Kubeconfigs anfordern, versiegelt für den eigenen Schlüssel des Aufrufers, und sie widerrufen. |
k3s:destructive | Cluster anlegen, löschen, abkoppeln und wiederherstellen, jeweils nur mit dem Namen des Clusters als getippter Bestätigung. |
Ein Schlüssel handelt mit den Rechten der Person, die ihn angelegt hat, in diesem Team. Mitglieder lesen; jede Änderung braucht einen Team-Admin; ein Mitglied bekommt eine view-Kubeconfig, es sei denn, das Team erlaubt admin. Ein Scope engt diese Rechte ein; er erweitert sie nie.
Ein Schlüssel gehört zu einem Team und reicht an jeden Cluster dieses Teams: Kein Scope lässt sich auf einen Cluster begrenzen. Ein Schlüssel mit k3s:destructive, der Wegwerf-Cluster anlegt, kann auch die Produktion löschen, wenn sie im selben Team liegt; halte die Produktion in einem Team, zu dem der Schlüssel nicht gehört.
Endpunkte
Abschnitt betitelt „Endpunkte“Die Pfade sind relativ zum Basispfad. „Admin“ heißt: Die Besitzerin oder der Besitzer des Schlüssels muss Team-Admin sein. Die Operation-ID ist das, was das Audit-Log als Aufruf festhält.
| Methode | Pfad | Scope | Admin | Antwort | Operation-ID |
|---|---|---|---|---|---|
| GET | projects/ | k3s:read | — | 200 {items: [{id, name, ref}]} | k3s_projects_list |
| GET | projects/{project}/server-types/ | k3s:read | — | 200 Standorte und Servertypen | k3s_projects_server_types |
| GET | clusters/ | k3s:read | — | 200 eine Seite Cluster | k3s_clusters_list |
| POST | clusters/ | k3s:destructive | ja | 202 {cluster} | k3s_clusters_create |
| GET | clusters/{cluster}/ | k3s:read | — | 200 der Cluster vollständig | k3s_clusters_get |
| DELETE | clusters/{cluster}/ | k3s:destructive | ja | 202 {cluster} | k3s_clusters_delete |
| GET | clusters/{cluster}/status/ | k3s:read | — | 200 Zustand und Gesundheit | k3s_clusters_status |
| GET | clusters/{cluster}/nodes/ | k3s:read | — | 200 {items: [node]} | k3s_clusters_nodes |
| POST | clusters/{cluster}/detach/ | k3s:destructive | ja | 200 {cluster} | k3s_clusters_detach |
| GET | clusters/{cluster}/snapshots/ | k3s:read | — | 200 eine Seite Snapshots, neueste zuerst | k3s_snapshots_list |
| POST | clusters/{cluster}/snapshots/ | k3s:write | ja | 202 {operation} | k3s_snapshots_create |
| POST | clusters/{cluster}/restore/ | k3s:destructive | ja | 202 {operation} | k3s_clusters_restore |
| GET | clusters/{cluster}/operations/ | k3s:read | — | 200 eine Seite Operationen, neueste zuerst | k3s_operations_list |
| GET | clusters/{cluster}/operations/{operation_id}/ | k3s:read | — | 200 die Operation mit ihren Ereignissen | k3s_operations_get |
| GET | clusters/{cluster}/upgrades/ | k3s:read | — | 200 Upgrade-Informationen | k3s_upgrades_get |
| POST | clusters/{cluster}/upgrades/ | k3s:write | ja | 202 {operation} | k3s_upgrades_schedule |
| GET | clusters/{cluster}/addons/ | k3s:read | — | 200 die Add-ons des Release | k3s_addons_list |
| PATCH | clusters/{cluster}/addons/{name}/ | k3s:write | ja | 200 das gespeicherte Add-on | k3s_addons_set |
| POST | clusters/{cluster}/access/ | k3s:access | für admin, wenn das Team es Mitgliedern nicht erlaubt | 202 {operationId, operation, resultUrl} | k3s_access_request |
| GET | clusters/{cluster}/access/{operation_id}/result/ | k3s:access | — | 202, dann einmal 200 | k3s_access_result |
| POST | clusters/{cluster}/access/revoke/ | k3s:access | für alle außer dir selbst | 202 {operation} | k3s_access_revoke |
Eine 202 heißt: Das Portal hat die Arbeit eingereiht. Änderungen an einem Cluster laufen als Operationen, die pbx-agent bei seiner nächsten Synchronisation abholt, etwa alle 30 Sekunden: Frag die Operation ab, bis ihr state succeeded oder failed ist. Einen neuen Cluster fragst du mit GET clusters/{cluster}/ ab, bis sein state ready ist.
Request-Bodies und Parameter
Abschnitt betitelt „Request-Bodies und Parameter“Einen Cluster anlegen: POST clusters/
Abschnitt betitelt „Einen Cluster anlegen: POST clusters/“Das Formular der Anlegen-Seite als JSON, geprüft von genau diesem Formular. Vor dem Formular prüft das Portal in dieser Reihenfolge: ein verbundenes Hetzner-Projekt, ein Release im Kanal stable, das Cluster-Limit des Teams, die akzeptierten Bedingungen und die Abrechnung. Der erste Cluster eines Teams startet sein Abonnement und wird im Portal angelegt, nicht über die API (402 payment_method_required); ebenso der erste Cluster, nachdem das Abonnement des Teams geendet hat. Geprüft wird das Formular gegen das Release im Kanal stable, auch bei channel: early.
| Feld | Typ | Pflicht | Standard und Grenzen |
|---|---|---|---|
name | String | ja | ≤ 100 Zeichen |
confirm | String | ja | der Name des neuen Clusters, noch einmal getippt |
project | Integer | ja | die ID des Hetzner-Projekts, aus GET projects/ |
slug | String | — | das DNS-Label, ≤ 32 Zeichen; aus dem Namen gebildet |
location | String | — | aus GET projects/{project}/server-types/ |
serverType | String | — | aus derselben Liste |
adoptServer | Objekt | — | statt eines neuen Servers: id und confirmName (der Name des Servers, getippt: Er wird neu aufgesetzt), keepVolumes und detachFirewalls (beide false) |
channel | String | — | stable (Standard) oder early |
topology | String | — | single |
maintenance | Objekt | — | days (Standard saturday, sunday), start 02:00, end 05:00, timezone Europe/Berlin |
allowedApiRanges | Liste von CIDRs | — | wer Port 6443 erreicht; Standard 0.0.0.0/0 und ::/0 |
backup | Objekt | — | holder portal (Standard) oder customer; s3 mit endpoint (Host, ohne Schema), bucket, folder, region, accessKey, secretKey; scheduleCron (Standard 0 */6 * * *); keepCycles 1–500 (Standard 28) |
hcloudToken | Objekt | — | mode separate (Standard) oder copy; token für separate |
platform | Objekt | — | profile, eines, das das Anlegen-Formular für das Add-on PaaSbox Platform anbietet; observability (Standard false). Seit paasbox-platform 1.0.2 (Release 2026.10.2) braucht die Plattform eine ACME-E-Mail (acmeEmail). Die Anlegen-Seite des Portals fragt danach; die API hat kein Feld dafür, darum wird ein Anlegen mit platform mit 422 validation_failed abgelehnt. Leg den Cluster ohne platform an und schalte die Plattform mit PATCH …/addons/paasbox-platform/ an |
Die anderen Bodies
Abschnitt betitelt „Die anderen Bodies“| Endpunkt | Body oder Query | Regeln |
|---|---|---|
GET projects/{project}/server-types/ | Query location | Standard fsn1, sonst der erste Standort des Projekts. Jeder Servertyp: name, arch, cores, memoryGB, diskGB, cpuType, monthlyNetEUR (Hetzners monatlicher Nettopreis). Hetzners Antwort wird 10 Minuten lang vorgehalten. |
GET clusters/, …/snapshots/, …/operations/ | Query page (ab 1), page_size (≤ 100, Standard 25) | Die Antwort ist {items, count, next, previous}. |
GET …/operations/ | Query state, type | Einer der Werte unten; alles andere ist 422. |
DELETE clusters/{cluster}/ | confirm, finalSnapshot (Standard true), als Body oder als Query-Parameter | Der Cluster zählt sofort nicht mehr für die Abrechnung (Kosten und Abrechnung); Server, Netz und Firewall verschwinden im Hintergrund, nach einem letzten Snapshot, es sei denn, finalSnapshot ist false. |
POST …/detach/ | confirm | Der Cluster läuft weiter; das Portal verwaltet und berechnet ihn nicht mehr. |
POST …/restore/ | snapshot (der Name aus der Snapshot-Liste), confirm | Der Cluster muss ready oder failed sein. Seine API steht, solange die Wiederherstellung läuft, und alles, was seit dem Snapshot geschrieben wurde, ist verloren. |
POST …/snapshots/ | keiner | Der Cluster muss laufen. |
POST …/upgrades/ | when: window oder now; release (optional) | Nur ein Cluster im Zustand ready ohne eingeplantes Upgrade. release ist eine Absicherung: abgelehnt, wenn ein anderes Release angeboten wird. window wartet auf das nächste Wartungsfenster In Arbeit; now läuft bei der nächsten Synchronisation und startet den Node neu. |
PATCH …/addons/{name}/ | enabled, options, also (alle optional) | Nur was genannt ist, ändert sich. options werden über die gespeicherten gelegt, null entfernt eine, geheime Optionen bleiben, wenn sie nicht mitgeschickt werden. also nennt andere Add-ons, die diese Änderung im selben Speichern schalten darf: eines, das dieses Add-on braucht, geht an; eines, mit dem es kollidiert, oder eines, das es braucht, wenn es ausgeht, geht aus. Es gelten die Prüfungen der Add-on-Seite: das Optionsschema, requires, conflicts und der Speicher gegen den Server. Ein Basis-Add-on lässt sich nicht ausschalten. Die PaaSbox Platform mit Flux: {"enabled": true, "options": {"profile": "saas-http01", "acmeEmail": "you@example.com"}, "also": ["flux"]}. |
POST …/access/ | publicKey, role (view Standard, oder admin), lifetimeSeconds (600–86400, Standard 3600) | publicKey ist Base64 des 65-Byte-Punkts (unkomprimiert) eines ECDH-P-256-Schlüssels, den du für diese Anfrage erzeugt hast. Der Cluster muss ready, upgrading oder restoring sein. |
GET …/access/{operation_id}/result/ | keiner | 202 {operationId, state, ready: false, retryAfterSeconds: 5}, solange pbx-agent arbeitet; 200 {operationId, state, ready: true, sealed, aad, expiresAt} genau einmal; 410 gone, nachdem sie abgeholt wurde oder 5 Minuten vergangen sind; 409 conflict, wenn die Anfrage fehlschlug. Nur der Schlüssel oder die Sitzung, die angefragt hat, kann sie abholen. |
POST …/access/revoke/ | user: me (Standard), all oder die ID oder E-Mail eines Mitglieds | Löscht die ServiceAccounts hinter den Kubeconfigs bei der nächsten Synchronisation. |
Die versiegelte Kubeconfig ist {alg, epk, nonce, ciphertext} mit alg ECDH-P256+HKDF-SHA256+A256GCM. So öffnest du sie: ECDH deines privaten Schlüssels mit epk, HKDF-SHA256 mit 32 Null-Bytes als Salt und der Info pbx-access-v1 auf 32 Bytes, dann AES-256-GCM mit nonce und der Operation-ID (aad, UTF-8) als zugehörigen Daten. Der Helfer des Portals, paasbox_kubeconfig.py, erledigt das (Einem Agenten Zugriff geben).
Antwortobjekte
Abschnitt betitelt „Antwortobjekte“| Objekt | Felder |
|---|---|
| Cluster | id, name, slug, state, location, serverType, arch, topology, project {id, name}, channel, autoPatchUpgrades, release, desiredRelease, apiEndpoint, publicIPv4, allowedApiRanges, unreachable, lastSeenAt, createdAt |
Cluster vollständig (GET clusters/{cluster}/) | der Cluster, dazu status, maintenance (mit dem nächsten Fenster), nodes, lastSnapshot, backup {holder, schedule, keep, target}, upgrade |
| Status | state, step, failure {during, step, message}, unreachable, unreachableSince, lastSeenAt, apiHealthy, generation, observedGeneration, certificatesExpireAt, flags ({level, text}, das Schlimmste zuerst), activeOperation, waitingOperations |
| Node | name, role, state, ready, leader, unschedulable, k3sVersion, release, arch, publicIPv4, diskFreeBytes, lastSeenAt |
| Snapshot | name, location (s3 oder local, eine Zeile pro Kopie), kind, takenAt, sizeBytes, node |
| Operation | id, type, state, params, requestedBy, windowBound, notBefore, notAfter, createdAt, acceptedAt, finishedAt, step, failedStep, message, result; mit events (seq, at, state, step, message, log, result) bei GET …/operations/{operation_id}/ |
| Upgrade-Informationen | current und available ({version, k3sVersion, status, paused, notes}, available mit patch), pending, channel, autoPatchUpgrades, canSchedule, nextWindow |
| Add-on | name, title, kind, version, enabled, editable, options, secretOptionsSet, optionsSchema, notEditableOptions, requires, conflicts, memoryMiB, minServerMemoryMiB, fits, memoryByOptions, reported |
| gespeichertes Add-on | addon, switched ([{addon, enabled}]), generation, note |
Keine Antwort enthält dein Hetzner-Token, die Schlüssel des Buckets oder die Tokens des Clusters; backup.target ist s3://<endpoint>/<bucket>/<folder>.
| Wertemenge | Werte |
|---|---|
state eines Clusters | pending, provisioning, ready, upgrading, restoring, deleting, deleted, detached, failed |
type einer Operation | snapshot.save, cluster.restore, cluster.upgrade, server.rejoin, node.drain, node.forget, agent.rotate_key, access.issue, access.revoke, diag.collect, s3.verify |
state einer Operation | queued, delivered, accepted, running, succeeded, failed, unsupported, expired, cancelled |
Zerstörerische Operationen
Abschnitt betitelt „Zerstörerische Operationen“Anlegen, Löschen, Abkoppeln und Wiederherstellen brauchen k3s:destructive, einen Team-Admin als Besitzer des Schlüssels und confirm:
- beim Löschen, Abkoppeln und Wiederherstellen den
namedes Clusters (nicht seinen Slug), exakt; - beim Anlegen den Namen des neuen Clusters, noch einmal getippt.
Fehlt confirm, scheitert schon die Prüfung des Bodys: 422 validation_failed mit field: "confirm"; jeder andere Wert als der Name ist 422 confirmation_required. Das Portal prüft den Namen, nicht, ob eine Person zugestimmt hat: Ein Agent, der den Namen kennt, kann ihn schicken. Ob dein Agent dich vorher fragt, liegt an seinem Client.
Jeder Fehler ist ein Problem-Dokument nach RFC 7807 (application/problem+json): type, title, status, detail, code, instance, operationId und field, wenn eine Eingabe schuld ist. Manche fügen Felder hinzu: requiredScope (insufficient_scope), errors nach Feld (validation_failed), canSwitchWith (Add-ons). Werte code aus.
| Status | code | Wann |
|---|---|---|
| 400 | invalid_request | Eine fehlerhafte Anfrage. |
| 401 | unauthenticated | Kein Schlüssel, oder einer, der unbekannt, widerrufen oder abgelaufen ist; oder seine Besitzerin oder sein Besitzer ist nicht mehr aktives Mitglied. |
| 402 | payment_method_required | Anlegen: der erste Cluster des Teams, der das Abonnement im Portal startet. |
| 402 | account_past_due | Anlegen: Das Konto des Teams ist im Zahlungsverzug. |
| 403 | insufficient_scope | Dem Schlüssel fehlt der Scope (requiredScope nennt ihn), oder er hat gar keinen k3s-Scope. |
| 403 | admin_required | Die Änderung braucht einen Team-Admin, oder ein Mitglied wollte eine admin-Kubeconfig, die das Team nicht erlaubt. |
| 403 | not_enabled | PaaSbox Clusters ist für das Team nicht freigeschaltet. |
| 403 | terms_not_accepted | Anlegen: Kein Team-Admin hat die Bedingungen im Portal akzeptiert. |
| 404 | not_found | Kein solches Team, kein solcher Cluster, Snapshot, keine solche Operation, kein Add-on, Projekt oder Mitglied, oder nicht deins. Der Cluster eines anderen Teams antwortet genauso. |
| 409 | conflict | Der Zustand des Clusters lässt es nicht zu, eine Operation ist schon eingereiht, kein Release wird angeboten, oder Hetzner hat das Token des Projekts abgelehnt. |
| 409 | quota_exceeded | Anlegen: das Cluster-Limit des Teams (10 gleichzeitig, mehr auf Anfrage). |
| 410 | gone | Die versiegelte Kubeconfig wurde schon abgeholt oder ist älter als 5 Minuten. |
| 422 | validation_failed | Ein Feld ist falsch; errors listet jedes Feld mit seinem API-Namen. |
| 422 | confirmation_required | confirm fehlt oder ist nicht der Name. |
| 429 | rate_limited | Zu viele Aufrufe; Retry-After nennt die Sekunden bis zum nächsten Versuch. |
| 500 | internal_error | Ein Fehler im Portal. |
| 503 | reconciler_unavailable | Die Hetzner-API hat mit einem Fehler geantwortet. |
Rate-Limits
Abschnitt betitelt „Rate-Limits“| Grenze | Standard |
|---|---|
| Aufrufe pro Minute, pro Schlüssel (pro Person bei einer Browser-Sitzung) | 120 |
| Davon ändernde Aufrufe (POST, PATCH, DELETE) pro Minute | 20 |
| Fenster | fest, eine Minute |
| Geteilt mit | den MCP-Tools desselben Schlüssels |
Jeder ändernde Aufruf und jede Anfrage nach einer Kubeconfig und ihre Herausgabe wird festgehalten, mit Schlüssel, Person, Operation-ID, Cluster, den Argumenten ohne Geheimnisse, dem Ergebnis und seinem Fehlercode. Auch Ablehnungen wegen eines fehlenden Scopes, einer Rolle, des Zustands des Clusters oder einer falschen Bestätigung werden festgehalten. Nicht festgehalten werden lesende Aufrufe, Abfragen einer Kubeconfig, die noch nicht fertig ist, und Aufrufe, die vor ihrer Prüfung abgelehnt werden: für einen unbekannten Schlüssel, das Rate-Limit oder einen fehlerhaften Body. Die Admins des Teams lesen das auf API keys & agents (Einem Agenten Zugriff geben).
OpenAPI-Schema
Abschnitt betitelt „OpenAPI-Schema“Das Portal liefert das OpenAPI-Dokument unter /api/schema/, mit Swagger UI unter /api/schema/swagger-ui/ und ReDoc unter /api/schema/redoc/. Die Endpunkte dieser Seite tragen Tags, die mit „PaaSbox Clusters“ beginnen; das Schlüsselschema heißt TeamKey.
Beispiel
Abschnitt betitelt „Beispiel“curl -H "Authorization: Bearer $PAASBOX_API_KEY" \ "$PAASBOX_PORTAL/api/v1/teams/acme/k3s/clusters/upcheck-prod/status/"Ein Löschen, abgelehnt, weil confirm nicht der Name des Clusters war:
{ "type": "https://app.paasbox.com/errors/confirmation_required", "title": "Typed confirmation required", "status": 422, "detail": "deleting a cluster needs confirm set to the cluster's name, exactly: 'upcheck-prod'", "code": "confirmation_required", "instance": "/api/v1/teams/acme/k3s/clusters/upcheck-prod/", "operationId": null, "field": "confirm"}MCP-Tools hat dieselben Operationen als Tools für einen Agenten, und Leitplanken für Agenten, was diese Regeln verhindern und was nicht.