Zum Inhalt springen

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.

PunktWert
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.
HeaderAuthorization: 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-SitzungEine angemeldete Sitzung funktioniert auf den REST-Endpunkten ebenfalls, mit den eigenen Rechten der Person und allen vier Scopes.
Bedingungen bei jedem AufrufDer Schlüssel ist weder widerrufen noch abgelaufen, seine Besitzerin oder sein Besitzer ist noch aktives Mitglied des Teams, und das Team hat PaaSbox Clusters.
FormatJSON, Felder in camelCase, Zeiten als RFC-3339-Strings in UTC.

Jeder Endpunkt braucht genau einen Scope. Ein Schlüssel hat die Scopes, die beim Anlegen angehakt wurden.

ScopeErlaubt
k3s:readCluster, ihre Gesundheit, Nodes, Snapshots, Operationen, Upgrades und Add-ons sehen, dazu die Hetzner-Projekte und Servertypen, auf denen ein Cluster entstehen kann.
k3s:writeSnapshots machen, Upgrades einplanen, Add-ons an- und ausschalten oder einstellen.
k3s:accessTemporäre Kubeconfigs anfordern, versiegelt für den eigenen Schlüssel des Aufrufers, und sie widerrufen.
k3s:destructiveCluster 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.

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.

MethodePfadScopeAdminAntwortOperation-ID
GETprojects/k3s:read—200 {items: [{id, name, ref}]}k3s_projects_list
GETprojects/{project}/server-types/k3s:read—200 Standorte und Servertypenk3s_projects_server_types
GETclusters/k3s:read—200 eine Seite Clusterk3s_clusters_list
POSTclusters/k3s:destructiveja202 {cluster}k3s_clusters_create
GETclusters/{cluster}/k3s:read—200 der Cluster vollständigk3s_clusters_get
DELETEclusters/{cluster}/k3s:destructiveja202 {cluster}k3s_clusters_delete
GETclusters/{cluster}/status/k3s:read—200 Zustand und Gesundheitk3s_clusters_status
GETclusters/{cluster}/nodes/k3s:read—200 {items: [node]}k3s_clusters_nodes
POSTclusters/{cluster}/detach/k3s:destructiveja200 {cluster}k3s_clusters_detach
GETclusters/{cluster}/snapshots/k3s:read—200 eine Seite Snapshots, neueste zuerstk3s_snapshots_list
POSTclusters/{cluster}/snapshots/k3s:writeja202 {operation}k3s_snapshots_create
POSTclusters/{cluster}/restore/k3s:destructiveja202 {operation}k3s_clusters_restore
GETclusters/{cluster}/operations/k3s:read—200 eine Seite Operationen, neueste zuerstk3s_operations_list
GETclusters/{cluster}/operations/{operation_id}/k3s:read—200 die Operation mit ihren Ereignissenk3s_operations_get
GETclusters/{cluster}/upgrades/k3s:read—200 Upgrade-Informationenk3s_upgrades_get
POSTclusters/{cluster}/upgrades/k3s:writeja202 {operation}k3s_upgrades_schedule
GETclusters/{cluster}/addons/k3s:read—200 die Add-ons des Releasek3s_addons_list
PATCHclusters/{cluster}/addons/{name}/k3s:writeja200 das gespeicherte Add-onk3s_addons_set
POSTclusters/{cluster}/access/k3s:accessfür admin, wenn das Team es Mitgliedern nicht erlaubt202 {operationId, operation, resultUrl}k3s_access_request
GETclusters/{cluster}/access/{operation_id}/result/k3s:access—202, dann einmal 200k3s_access_result
POSTclusters/{cluster}/access/revoke/k3s:accessfür alle außer dir selbst202 {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.

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.

FeldTypPflichtStandard und Grenzen
nameStringja≤ 100 Zeichen
confirmStringjader Name des neuen Clusters, noch einmal getippt
projectIntegerjadie ID des Hetzner-Projekts, aus GET projects/
slugString—das DNS-Label, ≤ 32 Zeichen; aus dem Namen gebildet
locationString—aus GET projects/{project}/server-types/
serverTypeString—aus derselben Liste
adoptServerObjekt—statt eines neuen Servers: id und confirmName (der Name des Servers, getippt: Er wird neu aufgesetzt), keepVolumes und detachFirewalls (beide false)
channelString—stable (Standard) oder early
topologyString—single
maintenanceObjekt—days (Standard saturday, sunday), start 02:00, end 05:00, timezone Europe/Berlin
allowedApiRangesListe von CIDRs—wer Port 6443 erreicht; Standard 0.0.0.0/0 und ::/0
backupObjekt—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)
hcloudTokenObjekt—mode separate (Standard) oder copy; token für separate
platformObjekt—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
EndpunktBody oder QueryRegeln
GET projects/{project}/server-types/Query locationStandard 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, typeEiner der Werte unten; alles andere ist 422.
DELETE clusters/{cluster}/confirm, finalSnapshot (Standard true), als Body oder als Query-ParameterDer 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/confirmDer Cluster läuft weiter; das Portal verwaltet und berechnet ihn nicht mehr.
POST …/restore/snapshot (der Name aus der Snapshot-Liste), confirmDer 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/keinerDer 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/keiner202 {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 MitgliedsLö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).

ObjektFelder
Clusterid, 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
Statusstate, step, failure {during, step, message}, unreachable, unreachableSince, lastSeenAt, apiHealthy, generation, observedGeneration, certificatesExpireAt, flags ({level, text}, das Schlimmste zuerst), activeOperation, waitingOperations
Nodename, role, state, ready, leader, unschedulable, k3sVersion, release, arch, publicIPv4, diskFreeBytes, lastSeenAt
Snapshotname, location (s3 oder local, eine Zeile pro Kopie), kind, takenAt, sizeBytes, node
Operationid, 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-Informationencurrent und available ({version, k3sVersion, status, paused, notes}, available mit patch), pending, channel, autoPatchUpgrades, canSchedule, nextWindow
Add-onname, title, kind, version, enabled, editable, options, secretOptionsSet, optionsSchema, notEditableOptions, requires, conflicts, memoryMiB, minServerMemoryMiB, fits, memoryByOptions, reported
gespeichertes Add-onaddon, 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>.

WertemengeWerte
state eines Clusterspending, provisioning, ready, upgrading, restoring, deleting, deleted, detached, failed
type einer Operationsnapshot.save, cluster.restore, cluster.upgrade, server.rejoin, node.drain, node.forget, agent.rotate_key, access.issue, access.revoke, diag.collect, s3.verify
state einer Operationqueued, delivered, accepted, running, succeeded, failed, unsupported, expired, cancelled

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 name des 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.

StatuscodeWann
400invalid_requestEine fehlerhafte Anfrage.
401unauthenticatedKein Schlüssel, oder einer, der unbekannt, widerrufen oder abgelaufen ist; oder seine Besitzerin oder sein Besitzer ist nicht mehr aktives Mitglied.
402payment_method_requiredAnlegen: der erste Cluster des Teams, der das Abonnement im Portal startet.
402account_past_dueAnlegen: Das Konto des Teams ist im Zahlungsverzug.
403insufficient_scopeDem Schlüssel fehlt der Scope (requiredScope nennt ihn), oder er hat gar keinen k3s-Scope.
403admin_requiredDie Änderung braucht einen Team-Admin, oder ein Mitglied wollte eine admin-Kubeconfig, die das Team nicht erlaubt.
403not_enabledPaaSbox Clusters ist für das Team nicht freigeschaltet.
403terms_not_acceptedAnlegen: Kein Team-Admin hat die Bedingungen im Portal akzeptiert.
404not_foundKein 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.
409conflictDer 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.
409quota_exceededAnlegen: das Cluster-Limit des Teams (10 gleichzeitig, mehr auf Anfrage).
410goneDie versiegelte Kubeconfig wurde schon abgeholt oder ist älter als 5 Minuten.
422validation_failedEin Feld ist falsch; errors listet jedes Feld mit seinem API-Namen.
422confirmation_requiredconfirm fehlt oder ist nicht der Name.
429rate_limitedZu viele Aufrufe; Retry-After nennt die Sekunden bis zum nächsten Versuch.
500internal_errorEin Fehler im Portal.
503reconciler_unavailableDie Hetzner-API hat mit einem Fehler geantwortet.
GrenzeStandard
Aufrufe pro Minute, pro Schlüssel (pro Person bei einer Browser-Sitzung)120
Davon ändernde Aufrufe (POST, PATCH, DELETE) pro Minute20
Fensterfest, eine Minute
Geteilt mitden 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).

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.

Terminal-Fenster
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.