Crafthoster
LEVEL UP!
LEVEL 1
Du bist jetzt auf Level 1!

API-Dokumentation

Integrationsübersicht der Crafthoster-Plattform – verfügbare Endpunkte und Integrationswege.

Überblick Öffentliche Endpunkte Backend-Proxy Authentifizierung Monitoring & Metriken Fehlercodes Frontend-Integration Redoc-Viewer OpenAPI JSON
Maschinenlesbare Spezifikation verfügbar
OpenAPI 3.1 – beschreibt alle öffentlichen und Integrations-Endpunkte. Verwende die Spec für SDK-Generierung, Linting oder als Redoc-/Swagger-Import.
Redoc-Viewer öffnen openapi.json herunterladen
Integrationsübersicht

Crafthoster ist eine webbasierte Plattform. Es gibt keine öffentliche REST-API unter einem /api/v1-Prefix. Für Browser-Flows wird Sitzungs-Cookie-Authentifizierung verwendet. Für Server-to-Server-Integration steht der API-Key-Flow über /backendserver/validateApiKey zur Verfügung. Für externe Integrationen stehen zwei Wege zur Verfügung:

Pfad Typ Beschreibung
/api/hubs/public
/api/hubs/packages
/api/hubs/<hub_id>
public Öffentlich lesbare Plattform-Endpunkte – kein Login erforderlich
/backendserver/<path> server→server Reverse-Proxy zum Auth-Backend – ausschließlich für serverseitige Aufrufe mit API-Key
Öffentliche Endpunkte

Diese Endpunkte sind ohne Authentifizierung erreichbar und geben öffentliche Plattformdaten zurück.

GET /api/hubs/public Aktive Hubs auflisten

Response 200

{ "ok": true, "hubs": [ { "hub_id": "hub_abc123", "name": "Mein Hub", "slug": "mein-hub", "tier": "hub_pro", "apps_count": 3, "tagline": "Willkommen bei Mein Hub", "primary_color": "#6366f1" } ] }
GET /api/hubs/packages Verfügbare Hub-Pakete

Response 200

{ "ok": true, "packages": [ /* Paketliste */ ] }
GET /api/hubs/{hub_id} Hub-Details abrufen

Öffentliche Hub-Felder werden ohne Login zurückgegeben. Interne Felder (Billing, API-Keys, Admins) sind nur für authentifizierte Hub-Admins sichtbar.

Response 200

{ "ok": true, "hub_id": "hub_abc123", "name": "Mein Hub", "slug": "mein-hub", "status": "active", "apps": [ /* ... */ ] }
GET /{slug}/api/packages Instanz-Pakete

Liefert die verfügbaren Pakete einer einzelnen Instanz (z. B. /game_hoster/api/packages).

Response 200

{ "packages": [ /* ... */ ] }
GET /{slug}/api/config Instanz-Konfiguration

Gibt die öffentliche Konfiguration einer Instanz zurück (Theme, Name, verfügbare Funktionen).

Response 200

{ "slug": "game_hoster", "name": "Game Hoster", "config": { /* öffentliche Felder */ } }
Instances

Endpunkte für das Abrufen von Instanz-Daten und -Paketen.

GET /instances Liste aller öffentlichen Instanzen

Gibt alle registrierten Instanzen zurück.

Backend-Proxy

Serverseitige Aufrufe an das Auth-Backend können über den Reverse-Proxy /backendserver/<path> geleitet werden. Dieser Endpunkt ist nicht für Browser gedacht – er erfordert einen internen API-Key und wird ausschließlich für Server-zu-Server-Kommunikation verwendet. Der API-Key wird über den X-API-Key-Header übergeben (Infrastructure-Level).

* /backendserver/{path} Auth-Backend-Proxy

Erforderliche Header (serverseitig)

Header Pflicht Beschreibung
X-API-Key Ja Serverseitiger API-Key (nicht im Browser verwenden)

Unterstützte Methoden

GET · POST · PUT · PATCH · DELETE

Timeout

Der Proxy wartet maximal 15 Sekunden auf eine Antwort des Upstream-Backends. Bei Überschreitung wird der Request abgebrochen und eine strukturierte Fehlerantwort zurückgegeben (504).

Retry-Verhalten

Automatische Wiederholversuche erfolgen ausschließlich für idempotente Methoden (GET, HEAD, OPTIONS) – maximal 3 Versuche bei Timeout oder Netzwerkfehler. POST, PUT, PATCH und DELETE werden nie automatisch wiederholt, um doppelte Nebeneffekte zu verhindern.

Circuit Breaker

Der Proxy enthält einen eingebauten Circuit Breaker, der bei wiederholten Upstream-Fehlern (5xx-Antworten, Timeouts, Verbindungsabbrüche) den Upstream temporär kurzschließt. Zustandsübergänge:

Zustand Verhalten
closed Normal – alle Requests werden durchgeleitet.
open Kurzgeschlossen – Requests werden sofort mit 503 circuit_breaker_open abgelehnt. Öffnet nach 5 aufeinanderfolgenden Fehlern (konfigurierbar via CB_FAILURE_THRESHOLD).
half-open Probe – nach 30 s (CB_RECOVERY_TIMEOUT) lässt der Breaker einen Probe-Request durch. Bei Erfolg wechselt er nach 2 aufeinanderfolgenden Erfolgen zurück zu closed; bei erneutem Fehler sofort zurück zu open.

Wenn der Breaker open ist, erhalten Clients folgende Fehlerstruktur:

{ "ok": false, "error": "Upstream vorübergehend nicht verfügbar (Circuit Breaker offen)", "error_type": "circuit_breaker_open", "retryable": false, "circuit_breaker": { "state": "open", "recovery_timeout_seconds": 30 } } // HTTP 503 Service Unavailable

Einheitliche Fehlerantwort

Alle Proxy-Fehler liefern eine konsistente JSON-Struktur:

{ "ok": false, "error": "…", // menschenlesbare Fehlermeldung "error_type": "timeout", // "timeout" | "upstream_error" | "network" "retryable": true // ob ein manueller Retry sinnvoll ist }

error_type-Werte

error_type HTTP-Status Ursache retryable
timeout 504 Upstream überschritt 15 s Timeout GET/HEAD/OPTIONS: true · andere: false
network 502 Verbindung zum Backend gescheitert GET/HEAD/OPTIONS: true · andere: false
upstream_error 4xx / 5xx Upstream lieferte HTTP-Fehlerantwort false
circuit_breaker_open 503 Circuit Breaker ist offen – Upstream kurzgeschlossen nach wiederholten Fehlern false

Edge Cases

  • Idempotente vs. nicht-idempotente Requests: Nur GET/HEAD/OPTIONS werden automatisch wiederholt. POST-Flows (z. B. Zahlungen, Registrierungen) erhalten nie einen automatischen Retry.
  • Langsamer Upstream: Nach 15 s liefert der Proxy sofort eine 504-Antwort. Der Upstream-Request im Backend wird ebenfalls abgebrochen.
  • Proxy-Abbruch durch Browser: Bricht der Browser die Verbindung, bevor der Proxy antwortet, wird kein Retry ausgelöst. Das Frontend erkennt dies als Netzwerkfehler (error_type: "network" oder PROXY_ERROR.OFFLINE).
  • Mehrfaches manuelles Retry: Das Feld retryable: true signalisiert dem Frontend, dass ein manueller Retry-Button angezeigt werden darf. Bei retryable: false soll kein Retry angeboten werden.
  • Kurzer Upstream-Blip: Einzelne Timeouts oder 5xx-Antworten öffnen den Breaker noch nicht – erst nach 5 aufeinanderfolgenden Fehlern (Standardwert). Einzelne Fehler tragen zum Zähler bei, ein erfolgreiches 2xx setzt ihn zurück.
  • Circuit Breaker in Multi-Prozess-Deployments: Der Breaker-Zustand ist In-Memory und pro Worker-Prozess (Gunicorn, Docker-Replicas). Jeder Prozess unterhält seinen eigenen Zähler. Für prozessübergreifende Synchronisierung kann der Zustand in Redis ausgelagert werden (Environment-Variable CB_STORAGE=redis – aktuell nicht aktiviert).
  • Browser-Retry parallel zum Proxy-Retry: Der Proxy wiederholt safe-Methoden intern bis zu 3-mal. Browser-seitige Retries sollten erst nach Empfang einer Fehlerantwort stattfinden, um Doppel-Requests zu vermeiden. proxyFetch() in utils.js führt keinen automatischen Browser-Retry durch.
Authentifizierung

Crafthoster unterscheidet zwei Authentifizierungskonzepte. Sie sind voneinander getrennt und dürfen nicht vermischt werden.

Flow Wo Credential Verwendung
Session-Cookie Browser → Hub Session-Token (Cookie) Browser-Flows: Login, Dashboard, Einstellungen
API-Key Server → /backendserver/* api_key im POST-Body Serverseitige Server-to-Server-Integration
Session-Cookie-Flow (Browser)

Beim Browser-Login liefert /backendserver/login einen session_token. Dieser Token identifiziert den eingeloggten Nutzer und wird für Dashboard-Aufrufe im Feld kunde_uuid der POST-Bodies mitgegeben. Kein Authorization-Header nötig — der Flow ist Cookie-basiert.

API-Key-Flow (Server-to-Server)

API-Keys werden vom Nutzer im Dashboard → Einstellungen → API-Keys erstellt. Der Key wird nur einmalig im Klartext angezeigt. Für die serverseitige Validierung wird der Key über /backendserver/validateApiKey geprüft.

Wichtig: API-Keys dürfen niemals im Frontend-Code oder Browser-Requests verwendet werden. Immer ausschließlich serverseitig einsetzen.

🔒 Scope-Modell & Berechtigungen

Jeder API-Key wird bei der Erstellung mit genau einem Scope versehen. Der Scope schränkt ein, welche Operationen mit dem Key erlaubt sind. Nicht passende Scopes werden vom Backend serverseitig abgelehnt ({"control": false, "error": "Insufficient scope"}). Alte Keys ohne Scope-Feld erhalten automatisch den Default-Scope full-access, um bestehende Integrationen nicht zu unterbrechen.

Scope Erlaubte Operationen Typischer Einsatz
read-only Lesen von Account-Daten (GET-äquivalente Endpunkte) Monitoring-Dashboards, Reports
billing-read Rechnungs- und Abrechnungsdaten abrufen Buchhaltungssysteme, ERP-Anbindungen
integration-write Schreibende Operationen für externe Integrationen Webhooks, CI/CD-Pipelines, Automatisierungen
full-access Alle Operationen (höchste Berechtigung) Vertrauenswürdige Backend-Server – sparsam einsetzen
⚠️ Least-Privilege-Prinzip: Verwenden Sie stets den am wenigsten privilegierten Scope, der für die Integration nötig ist. Keys mit full-access sollten nur für vollständig vertrauenswürdige, isolierte Server vergeben werden. Unbekannte oder leere Scope-Listen werden vom Backend abgelehnt.
POST /backendserver/validateApiKey API-Key prüfen (serverseitig)

Prüft ob ein API-Key gültig und aktiv ist. Gibt bei Erfolg die kunde_uuid des zugehörigen Accounts sowie die zugewiesenen Scopes zurück. Optional kann ein required_scope übergeben werden — der Endpunkt lehnt den Key ab, wenn er den geforderten Scope nicht besitzt (außer er hat full-access). Nur serverseitig aufrufen — nicht aus dem Browser. Rate-Limit: 20 Anfragen / Minute.

Request-Body

{ "api_key": "dlr_live_xxxxxxxxxxxx", "required_scope": "billing-read" // optional – lässt nur Keys mit diesem Scope durch }

Response 200 — gültiger Key

{ "control": true, "kunde_uuid": "<permanente Account-UUID>", "scopes": ["billing-read"] }

Response — ungültiger / widerrufener Key

{ "control": false }

Response — Scope nicht ausreichend

{ "control": false, "error": "Insufficient scope" }
⚠️ Hinweis: /backendserver/validateApiKey gibt eine rohe kunde_uuid zurück — das ist die permanente Account-UUID, kein Session-Token. Endpunkte wie /backendserver/getInstanceUserPackage erwarten im Feld kunde_uuid einen gültigen Session-Token. Für solche Flows muss der Nutzer zunächst über den normalen Login-Flow authentifiziert werden.
POST /auth/login Session-Login (Browser-Flow)

Authentifiziert den Nutzer und gibt einen Session-Token zurück. Der Token wird als Cookie gesetzt und für nachfolgende Anfragen verwendet.

DELETE /auth/session Session beenden (Logout)

Beendet die aktuelle Nutzersitzung und entfernt den Session-Cookie.

📊 Monitoring & Metriken

Die Plattform stellt Endpunkte für Health-Checks, Service-Gesundheit, Uptime-Verlauf und Prometheus-kompatible Metriken bereit. Service-Daten werden intern für 30 Sekunden gecacht. Bei Ausfall eines externen Checks liefert /api/status/services "monitoring_ok": false und die betroffenen Services werden als Partial Data gekennzeichnet.

GET /health Einfacher Liveness-Check

Öffentlich erreichbar. Gibt 200 zurück, solange der Prozess läuft. Kein Auth erforderlich. Geeignet für Load-Balancer-Health-Probes.

Response 200

{ "status": "ok" }
GET /metrics Prometheus-Metriken (Admin / intern)

Gibt Metriken im Prometheus Text Exposition Format 0.0.4 zurück. Zugriff nur für Platform-Admins oder mit Header X-Internal-Token: <INTERNAL_API_TOKEN>.

Metric NameTypeBeschreibung
deeliar_app_infogaugeAnwendungsmetadaten; Label version
deeliar_uptime_secondsgaugeSekunden seit App-Start (Prozess-Uptime)
deeliar_http_requests_totalcounterGesamtanzahl HTTP-Anfragen
deeliar_http_requests_by_statuscounterAnfragen aufgeschlüsselt nach HTTP-Statuscode; Label code
deeliar_http_requests_by_methodcounterAnfragen aufgeschlüsselt nach HTTP-Methode; Label method
deeliar_http_request_duration_seconds_sumsummaryKumulierte Bearbeitungszeit in Sekunden
deeliar_http_request_duration_seconds_countsummaryAnzahl gemessener Anfragen (Nenner für avg)
deeliar_instances_totalgaugeAnzahl registrierter Instanzen
deeliar_hubs_totalgaugeGesamtanzahl App-Hubs
deeliar_hubs_activegaugeAnzahl aktiver Hubs
deeliar_hubs_apps_totalgaugeApps über alle Hubs

Beispielantwort (Auszug)

# HELP deeliar_uptime_seconds Time since app started # TYPE deeliar_uptime_seconds gauge deeliar_uptime_seconds 3721.4 # HELP deeliar_http_requests_total Total HTTP requests # TYPE deeliar_http_requests_total counter deeliar_http_requests_total 18420 # HELP deeliar_http_requests_by_status HTTP requests by status code # TYPE deeliar_http_requests_by_status counter deeliar_http_requests_by_status{code="200"} 17843 deeliar_http_requests_by_status{code="404"} 501 deeliar_http_requests_by_status{code="500"} 76

Content-Type: text/plain; version=0.0.4; charset=utf-8

GET /api/status/services Live Service-Gesundheit (gecacht 30 s)

Gibt den Echtzeit-Status aller Plattform-Services zurück. Ergebnisse werden 30 Sekunden gecacht. monitoring_ok: false zeigt an, dass mindestens ein externer Check (z.B. Auth-Backend-Ping) fehlschlug — andere Services bleiben trotzdem sichtbar (Partial-Data-State). Ohne Admin-Recht werden Details wie Latenzen, interne Quellen und Probe-Details als public_summary redigiert.

Response 200

{ "ok": true, "monitoring_ok": true, /* false = partial data */ "services": [ { "key": "hub", "status": "up", "latency_ms": null, "source": "internal" }, { "key": "dashboards", "status": "up", "latency_ms": null, "source": "internal" }, { "key": "api", "status": "up", "latency_ms": null, "source": "internal_metrics" }, { "key": "auth", "status": "up", "latency_ms": 12, "source": "http_ping" }, { "key": "cdn", "status": "up", "latency_ms": null, "source": "fs_check" }, { "key": "email", "status": "up", "latency_ms": null, "source": "smtp_config" } ] }

status-Werte: "up" | "degraded" | "down"
source-Werte: "internal" | "internal_metrics" | "http_ping" | "fs_check" | "env_check" | "smtp_config" | "smtp_tcp" | "public_summary"

GET /api/status/uptime 30-Tage-Uptime-Verlauf

Liefert für jeden der letzten 30 Tage einen Statuswert. Tage vor dem App-Start werden als "unknown" markiert. Tage mit Einträgen in incidents.json werden als "degraded" gewertet. Negative Uptime-Werte (Uhrzeitsprünge) werden automatisch auf 0 korrigiert.

Response 200

{ "ok": true, "days": [ { "date": "2026-03-26", "status": "unknown" }, { "date": "2026-04-01", "status": "degraded" }, { "date": "2026-04-24", "status": "up" } ] }

Array hat immer genau 30 Einträge, chronologisch aufsteigend. status-Werte: "up" | "degraded" | "unknown"

GET /api/status/incidents Incidents-Liste (Fallback-Quelle)

Gibt alle Incidents aus incidents.json zurück. Diese Quelle dient als Fallback wenn der primäre Monitoring-Collector nicht erreichbar ist. Wird auch für die Uptime-Balken in der Status-Seite verwendet.

Response 200

{ "ok": true, "incidents": [ { "title": "Erhöhte Latenz API", "date": "2026-04-10", "date_display": "10. April 2026", "desc": "Kurzfristig erhöhte Antwortzeiten aufgrund DB-Last", "resolved_display": "Behoben um 14:32 UTC" } ] }
GET /api/status/probes Externe synthetische Probes (gecacht 60 s)

Führt externe synthetische HTTP-, DNS- und TLS-Checks durch und gibt deren Ergebnisse zurück. Ergebnisse werden 60 Sekunden gecacht. probes_ok: false bedeutet, dass mindestens eine Probe fehlschlug (Partial-Data-State). Die TLS-Probe ist nur aktiv, wenn PLATFORM_URL ein HTTPS-Schema hat. Ohne Admin-Recht werden IPs, Zertifikatsdetails und Latenzen redigiert.

Response 200

{ "ok": true, "probes_ok": true, /* false = min. 1 Probe fehlgeschlagen */ "probes": [ { "key": "probe_http", "label": "HTTP-Probe", "status": "up", "latency_ms": 84, "detail": "200 OK", "source": "synthetic_http" }, { "key": "probe_dns", "label": "DNS-Probe", "status": "up", "latency_ms": 5, "detail": "93.184.216.34", "source": "synthetic_dns" }, { "key": "probe_tls", "label": "TLS-Probe", "status": "up", "latency_ms": 120, "detail": "Gültig noch 74 Tage", "source": "synthetic_tls" } ] }

status: "up" | "degraded" | "down" | "unknown"
source: "synthetic_http" | "synthetic_dns" | "synthetic_tls" | "public_summary"
Konfiguration über Env-Variable PLATFORM_URL (z. B. https://deeliar.com).

GET /api/status/history Langzeit-Uptime-Verlauf (bis 365 Tage)

Gibt pro-Tag-Einträge für den angeforderten Zeitraum zurück. Die Daten stammen aus einer on-disk gespeicherten Zeitreihe (status_history.json), die vom Hintergrundprozess stündlich aktualisiert wird. Tage vor App-Start sind "unknown". Incidents überschreiben den Status auf "degraded".

Edge Cases: Datenlücken werden mit "up" gefüllt (konservativ). Tage mit Incidents ohne Snapshot-Eintrag haben source: "incident". Zeitreihen-Einträge haben source: "history".

Query-Parameter

ParameterTypDefaultBeschreibung
range integer 90 Anzahl Tage (1–365)

Response 200

{ "ok": true, "range": 90, "days": [ { "date": "2026-01-27", "status": "unknown", "uptime_pct": null, "incident_count": 0, "source": "derived" }, { "date": "2026-04-01", "status": "degraded", "uptime_pct": null, "incident_count": 1, "source": "incident" }, { "date": "2026-04-27", "status": "up", "uptime_pct": 99.8, "incident_count": 0, "source": "history" } ] }

source: "history" (Snapshot vorhanden) | "incident" (aus Incidents-Log) | "derived" (kein Datenpunkt)

Fehlercodes

Standardisierte HTTP-Statuscodes, die von der Plattform zurückgegeben werden.

StatusBeschreibung
200Anfrage erfolgreich
400Ungültige Anfrage / Validierungsfehler
401Nicht authentifiziert
429Rate Limit überschritten
502Backend nicht erreichbar (error_type: "network") – manuelle Wiederholung möglich
504Proxy-Timeout nach 15 s (error_type: "timeout") – manuelle Wiederholung bei GET/HEAD/OPTIONS möglich

Rate Limits

EndpunktLimit
/api/hubs/public30 Anfragen / Minute
/api/hubs/packages, /api/hubs/<hub_id>Plattformstandard (200/min)
/backendserver/* (Backend-Proxy)30 Anfragen / Minute
/backendserver/validateApiKey20 Anfragen / Minute
/backendserver/createApiKey
Body: key_name, scopes (z.B. ["read-only"])
5 Anfragen / Minute
/backendserver/revokeApiKey10 Anfragen / Minute
/backendserver/listApiKeys
Response enthält scopes-Array je Key
30 Anfragen / Minute
Standard (alle anderen Routen)Plattformstandard (200/min)

Rate-Limit-Response-Header

Jede Antwort von einem rate-limitierten Endpunkt enthält folgende Header. OPTIONS-Requests (CORS-Preflight) sind ausgenommen.

HeaderBeschreibung
X-RateLimit-LimitKonfiguriertes Limit für diesen Endpunkt (Anzahl Anfragen pro Fenster)
X-RateLimit-RemainingVerbleibende Anfragen im aktuellen Fenster (0 bei einem 429)
X-RateLimit-ResetUnix-Timestamp (Sekunden), wann das Fenster zurückgesetzt wird
Retry-AfterNur bei 429: Sekunden bis zum Reset

429-Response-Schema

{ "ok": false, "success": false, "error": "Too many requests. Please slow down.", "limit": 30, // konfiguriertes Limit (Anzahl) "retry_after": 60, // Sekunden bis zum Reset "reset_at": 1700000000 // Unix-Timestamp des Resets }

Bei Redis-Ausfall greift die In-Memory-Fallback-Strategie (swallow_errors=True): Anfragen werden durchgelassen und die Header können fehlen. Dieses Verhalten ist bewusst fail-open, damit ein Redis-Ausfall keine Dienstunterbrechung verursacht.

💻 Frontend-Integration (utils.js)

utils.js stellt proxyFetch() und proxyFetchWithState() bereit. Beide Funktionen liefern strukturierte Ergebnisse und ermöglichen es Screens, Loading-, Error- und Success-Zustände getrennt zu behandeln.

proxyFetch – Rückgabestruktur

{ "ok": true | false, "data": /* geparster JSON-Body oder null */, "errorType": "timeout" | "upstream_error" | "network" | "offline" | null, "retryable": true | false, "status": 200 /* HTTP-Status; 0 bei Offline/Netzwerkabbruch */ }

Beispiel: UI-Zustände (Loading / Error / Success)

// 1. Einfaches proxyFetch const result = await proxyFetch('GET', '/backendserver/status'); if (result.ok) { showSuccess(result.data); } else if (result.errorType === PROXY_ERROR.TIMEOUT) { showError('Zeitüberschreitung', result.retryable); } else if (result.errorType === PROXY_ERROR.OFFLINE) { showError('Keine Internetverbindung', true); } else { showError('Fehler: ' + (result.data?.error || 'Unbekannt'), result.retryable); } // 2. Mit automatischen State-Callbacks await proxyFetchWithState('POST', '/backendserver/action', payload, { onLoading: () => setLoadingState(true), onSuccess: (data) => { setLoadingState(false); renderResult(data); }, onError: (errorType, retryable, data) => { setLoadingState(false); showErrorBanner(data?.error, retryable); // retryable=false für POST } });

Wichtige Regel: POST nie automatisch wiederholen

Bei POST/PUT/PATCH/DELETE gibt der Proxy retryable: false zurück. Das Frontend darf keinen automatischen Retry auslösen – nur ein manueller Nutzerklick ist erlaubt, und nur wenn der Screen explizit für Idempotenz ausgelegt ist.