Projekte bauen, die auf KynxGate perfekt laufen
Diese Anleitung zeigt, wie du dein Repository strukturierst, damit es zuverlässig deployt und online bleibt – und wie du dein Projekt per API Control automatisierst: Status, Live-Logs, Redeploys – und mit der Browser-Test-API echte End-to-End-Tests durch autonome Agenten (KynxAI, CI, eigene Bots).
Überblick
KynxGate deployt dein Git-Repository in einem isolierten Container und macht es unter einer eigenen Domain erreichbar. Du brauchst kein spezielles Framework – wichtig ist nur, dass dein Dienst auf dem von KynxGate gesetzten Port und auf 0.0.0.0 lauscht.
Der Ablauf eines Deployments:
- Code wird aus dem verbundenen Repository (Branch) geklont.
- Abhängigkeiten installieren → Build → Container starten.
- Health-Check: warten, bis dein Dienst antwortet.
- Routing & SSL für die Domain werden automatisch eingerichtet.
Schnellstart
Ein minimaler Webdienst, der überall auf KynxGate funktioniert. Entscheidend sind die letzten beiden Zeilen – lausche auf process.env.PORT und 0.0.0.0:
// Funktioniert mit jeder Sprache – Beispiel Node.js / Express
const express = require("express")
const app = express()
// KynxGate setzt PORT, HOST und HOSTNAME automatisch.
const PORT = process.env.PORT || 3000
app.get("/health", (_req, res) => res.json({ ok: true }))
app.get("/", (_req, res) => res.send("Hallo von KynxGate!"))
// WICHTIG: auf 0.0.0.0 lauschen, NICHT auf 127.0.0.1/localhost
app.listen(PORT, "0.0.0.0", () => {
console.log("läuft auf 0.0.0.0:" + PORT)
})start-Script in der package.json, wird es automatisch erkannt. Für andere Sprachen (Go, Rust, Python …) gilt dasselbe Prinzip: Port aus der Umgebung lesen, auf 0.0.0.0 binden.Port & Host
KynxGate setzt beim Start automatisch diese Umgebungsvariablen in deinen Container:
PORT– der Port, auf dem dein Dienst lauschen muss. Nutze immerprocess.env.PORT, hardcode niemals einen festen Port.HOSTundHOSTNAMEsind auf0.0.0.0gesetzt.
127.0.0.1 / localhost lauscht, ist er von außerhalb des Containers nicht erreichbar – der Health-Check schlägt fehl und das Projekt erscheint offline (503), obwohl die Logs „listening" zeigen. Binde immer an 0.0.0.0.Frameworks wie Next.js, Express, Fastify und NestJS werden erkannt und passend mit PORT/HOST versorgt.
Build & Start
Install-, Build- und Start-Befehle kannst du in den Projekt-Einstellungen überschreiben. Ohne Angabe gilt eine sinnvolle Automatik:
- Paketmanager: automatisch erkannt –
bun.lock→ Bun,yarn.lock→ yarn,pnpm-lock.yaml→ pnpm, sonst npm. - Install:
bun install --frozen-lockfile,npm ci(bzw. yarn/pnpm --frozen-lockfile), Fallbacknpm install, wenn keine Lockfile vorhanden ist. devDependencies werden mitinstalliert (z. B. TypeScript, Vite). - Build: Standard
npm run buildbzw.bun run buildbei Bun-Projekten. - Start:
npm start(fallsscripts.startinpackage.json), sonst Framework-Fallback (z. B. Vite →vite preview), sonstnpm run dev, sonstnode index.js/node server.js.
scripts.start in der package.json (oder setze den Start-Befehl in den Projekt-Einstellungen). Ohne start kann KynxGate auf npm run dev fallen; das ist für Production oft falsch und erscheint im Build-Log als [deploy-ready] code=START_USES_DEV_FALLBACK.bun.lock, package-lock.json, yarn.lock oder pnpm-lock.yaml) – das macht Builds reproduzierbar und schneller.Vite & Static SPAs
Reine Vite-Frontends (React/Vue/Svelte ohne eigenes Backend) bauen nach dist/. Der Dev-Server (npm run dev) ist nicht für Deployments gedacht — er crasht im Container oft mit EROFS und lauscht nicht zuverlässig auf 0.0.0.0:$PORT.
Minimale package.json für KynxGate:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"start": "vite preview --host 0.0.0.0 --port ${PORT:-4173}"
}
}[deploy-ready] code=… severity=… in den Build-Logs (auch per API abrufbar) — z. B. fehlendes start-Script, 127.0.0.1 statt 0.0.0.0, oder fester Port.Alternative zu vite preview: npx serve dist -l tcp://0.0.0.0:$PORT (Paket serve als dependency). kynxgate.json ist optional und steuert primär den Health-Check — nicht den Start-Befehl.
Dockerfile & Compose
Hat dein Repo ein Dockerfile, nutzt KynxGate es direkt – ideal für eigene Stacks (Go, Rust, Python …). EXPOSE und Healthcheck werden zur Laufzeit mit dem echten PORT überschrieben. Ohne Dockerfile wird automatisch ein passendes Image erzeugt.
FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . RUN npm run build # PORT, HOST & HOSTNAME werden von KynxGate zur Laufzeit gesetzt. EXPOSE 3000 CMD ["npm", "start"]
Mehrere Container? Eine docker-compose.yml wird ebenfalls unterstützt (z. B. App + Worker oder Game-Server). Der Haupt-Dienst muss weiterhin auf 0.0.0.0:$PORT erreichbar sein.
Health-Checks
Nach dem Start prüft KynxGate, ob dein Dienst antwortet, bevor er live geschaltet wird. Der Pfad wird in dieser Reihenfolge bestimmt:
- Projekt-Einstellungen (UI) → „Health-Check-Pfad"
- Repo-Konfiguration:
kynxgate.json,package.json(Feldkynxgate),railway.jsonoderrailway.toml - Standard:
/
{
"healthcheckPath": "/health",
"healthcheckTimeout": 120
}/health-Endpoint an, der nur 200 OK zurückgibt, sobald dein Dienst wirklich bereit ist (DB verbunden etc.). Das macht Deployments stabiler und Zero-Downtime zuverlässig.Env-Variablen & Secrets
Umgebungsvariablen und Secrets pflegst du im Dashboard unter Variablen. Sie stehen deinem Dienst zur Laufzeit als process.env.* zur Verfügung. Secrets werden verschlüsselt gespeichert.
# Im Dashboard unter "Variablen" gesetzt, im Code so nutzen: const dbUrl = process.env.DATABASE_URL // automatisch bei verbundener DB const apiKey = process.env.STRIPE_SECRET // eigene Secrets const data = process.env.VOLUME_PATH // persistenter Speicher (Volume)
.gitignore für lokale .env-Dateien.Datenbanken & Volumes
Datenbanken (PostgreSQL, MySQL, MongoDB, Redis …) erstellst du im Dashboard und verbindest sie mit dem Projekt. Die Verbindungsdaten werden automatisch als Umgebungsvariable injiziert (z. B. DATABASE_URL).
Volumes bieten persistenten Speicher, der Deployments und Neustarts überlebt. Der Mount-Pfad steht als VOLUME_PATH bereit (Standard /data). Schreibe dauerhafte Dateien (Uploads, SQLite, Caches) dorthin – das Container- Dateisystem selbst ist flüchtig.
Domains & SSL
Jedes Projekt bekommt automatisch eine Subdomain: <name>.up.kynxgate.de – inkl. HTTPS-Zertifikat (Let's Encrypt). Eine eigene Domain kannst du zusätzlich verbinden; das Zertifikat wird automatisch ausgestellt und erneuert.
Host-Headers. Dein Dienst muss dafür nichts tun – einfach auf 0.0.0.0:$PORT lauschen.Zero-Downtime (Blue-Green)
Optional kannst du Zero-Downtime-Deployments aktivieren: Die neue Version startet parallel, und erst nach erfolgreichem Health-Check wird umgeschaltet. Voraussetzung ist ein sauberer /health-Endpoint und ein eigenes Dockerfile. Nicht geeignet für reine Compose-/Game-Server- Setups (z. B. CS2, TeamSpeak). Bei KynxAI nur mit aktivem Docker-Socket-Modus (allowDockerSocket) nicht verfügbar — normale KynxAI-Deploys unterstützen Blue-Green.
Logs
Alles, was dein Dienst nach stdout/stderr schreibt, landet in den Laufzeit-Logs. Zusätzlich werden Build- und Deploy-Logs pro Deployment gespeichert. Du siehst sie live im Dashboard – oder rufst sie per API ab (siehe unten).
Best-Practice-Checkliste
- Dienst lauscht auf process.env.PORT und 0.0.0.0
- Schlanker /health-Endpoint vorhanden
- Lockfile committed (reproduzierbare Builds)
- Secrets über Variablen, nicht im Repo
- Persistente Daten ins Volume (VOLUME_PATH)
- scripts.start in package.json (0.0.0.0 + $PORT) oder eigenes Dockerfile
- Vite/React-SPA: vite preview oder serve dist — nicht npm run dev
- Aussagekräftige Logs nach stdout/stderr
API Control – Überblick
Jedes Projekt hat eine eigene, projektgebundene REST-API unter /api/v1. Damit fragst du Status & Logs ab, löst Redeploys aus – und steuerst einen echten Chromium-Browser für automatische End-to-End-Tests (ideal für KynxAI und andere Agenten).
Du erstellst API-Keys direkt im Projekt: Projekt → Einstellungen → API Control. Jeder Key gilt nur für genau dieses Projekt und besitzt fein granulare Berechtigungen (Scopes).
GET /api/v1 — globale Übersicht mit agentOnboarding-Schritten. GET /api/v1/docs — vollständige maschinenlesbare Doku (JSON). GET /api/v1/docs?format=markdown — dieselbe Doku als Markdown für LLM-Agenten.Maschinenlesbare Doku für Agenten
Agenten sollen nicht die HTML-Doku scrapen — stattdessen gibt es dedizierte JSON/Markdown-Endpunkte. Beim ersten API-Test (Overview oder Verbindungstest im API-Control-Panel) bekommst du docsApi, docsMarkdown und agentOnboarding zurück.
# 1) Global (keine Auth) curl -s https://kynxgate.de/api/v1 | jq . curl -s "https://kynxgate.de/api/v1/docs?format=markdown" # 2) Projektgebunden (mit API-Key) — gefiltert nach deinen Scopes, echte URLs curl -s "https://kynxgate.de/api/v1/projects/<workspaceId>/<projectName>/docs?format=markdown" \ -H "Authorization: Bearer $KYNX_API_KEY" # 3) Einzelner Abschnitt (z. B. nur Browser) curl -s "https://kynxgate.de/api/v1/docs?section=browser&format=markdown"
GET /api/v1/projects/…/(Overview) — Handshake: Scopes, Endpunkte,nextSteps, Doku-URLsGET /api/v1/projects/…/docs— scope-gefilterte Doku mit projektspezifischen URLsGET /api/v1/projects/…/status— enthältapiHintsmit Verweis auf Doku-API
Authentifizierung & Keys
Sende den Key bei jeder Anfrage in einem dieser Header:
Authorization: Bearer kgp_xxxx_xxxxxxxxxxxxxxxxxxxx # oder X-Api-Key: kgp_xxxx_xxxxxxxxxxxxxxxxxxxx
Verfügbare Scopes (Least-Privilege – vergib nur, was nötig ist):
| Scope | Erlaubt |
|---|---|
| status:read | Online-/Deploy-Status abfragen |
| logs:read | Container-, Build- & Deploy-Logs (inkl. Live-Stream) |
| deployments:read | Deployment-Historie & Details |
| deploy:write | Redeploy auslösen |
| control:write | Projekt stoppen / Laufzeit steuern |
| browser:read | Browser-Sessions lesen, Screenshots/DOM/Console/Network/Diagnostics |
| browser:write | Browser-Session starten, Aktionen & Assertions ausführen |
Endpunkte
Basis-URL (die exakte URL inkl. IDs steht im API-Control-Panel deines Projekts):
https://kynxgate.de/api/v1/projects/{workspaceId}/{projectName}| Methode | Pfad | Scope | Zweck |
|---|---|---|---|
| GET | / | status:read | Übersicht + Scopes deines Keys |
| GET | /docs | status:read | Maschinenlesbare Doku (scope-gefiltert) |
| GET | /status | status:read | Online-/Deploy-Status |
| GET | /deployments | deployments:read | Deployment-Historie |
| GET | /logs?source=runtime|build|deploy | logs:read | Logs abrufen |
| GET | /logs/stream | logs:read | Live-Stream (SSE) |
| POST | /redeploy | deploy:write | Redeploy auslösen |
| POST | /stop | control:write | Projekt stoppen |
| POST | /browser/sessions | browser:write | Browser-Session starten |
| GET | /browser/sessions | browser:read | Aktive Sessions auflisten |
| GET | /browser/sessions/{id} | browser:read | Session-Status |
| DELETE | /browser/sessions/{id} | browser:write | Session schließen |
| POST | /browser/sessions/{id}/actions | browser:write | Klick, Tippen, Assertions (closeAfter) |
| GET | /browser/sessions/{id}/diagnostics | browser:read | Browser + Docker-Logs kombiniert |
| GET | /browser/sessions/{id}/screenshot | browser:read | Screenshot (PNG base64) |
| GET | /browser/sessions/{id}/dom | browser:read | HTML-DOM |
| GET | /browser/sessions/{id}/a11y | browser:read | Accessibility-Baum |
| GET | /browser/sessions/{id}/console | browser:read | Browser-Console (tail) |
| GET | /browser/sessions/{id}/network | browser:read | Network-Events (failedOnly) |
| GET | /browser/sessions/{id}/cookies | browser:read | Cookies |
| GET | /browser/sessions/{id}/storage | browser:read | localStorage / sessionStorage |
Beispiel-Antwort von /status:
{
"project": "kynxhotel",
"workspaceId": "cml18wa4e0003...",
"state": "online",
"online": true,
"url": "https://kynxhotel.up.kynxgate.de",
"subdomainUrl": "https://kynxhotel.up.kynxgate.de",
"customDomain": null,
"port": 9101,
"branch": "main",
"deployment": {
"id": "k7s9...",
"status": "SUCCESS",
"branch": "main",
"commitHash": "a1b2c3d",
"buildTimeMs": 48213,
"completedAt": "2026-06-16T12:50:05.000Z"
},
"checkedAt": "2026-06-16T17:55:00.000Z"
}Browser-Tests für Agenten
Die Browser-Test-API lässt externe Agenten eine echte Chromium-Instanz fernsteuern – wie ein Mensch mit Maus und Tastatur: Seiten öffnen, klicken, Formulare ausfüllen, einloggen, Dateien hochladen, Assertions prüfen, Screenshots machen.
Der Clou bei KynxGate: Du kontrollierst den gesamten Stack. Bei einem Fehler liefert /diagnostics Browser-Console, fehlgeschlagene Network-Requests und Docker-Runtime-Logs in einem Call – der Agent muss nicht raten, ob das Problem Frontend oder Backend ist.
Typischer Ablauf für einen autonomen Agenten:
- Deploy auslösen (
POST /redeploy) und warten bisonline: true - Browser-Session starten (
POST /browser/sessions) - Aktionen ausführen + Assertions (
POST .../actionsmitcloseAfter: true) - Bei Fehler:
/diagnostics→ Code anpassen → erneut deployen → wiederholen
# Voraussetzung: Projekt ist online (GET /status → online: true)
# API-Key mit browser:read + browser:write (Projekt → API Control)
export KEY="kgp_xxxx_xxxxxxxxxxxxxxxxxxxx"
export BASE="https://kynxgate.de/api/v1/projects/<workspaceId>/<projectName>"
# 1) Session starten
# target: "public" = Live-URL (Apache → Proxy → Container)
# "internal" = http://127.0.0.1:<PORT> (nur vom Server)
SESSION=$(curl -s -X POST "$BASE/browser/sessions" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"target":"public"}' | jq -r .sessionId)
# 2) Aktionen + automatisch schließen (closeAfter: true — empfohlen für Agenten!)
curl -s -X POST "$BASE/browser/sessions/$SESSION/actions" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"closeAfter": true,
"actions": [
{"type":"goto","path":"/"},
{"type":"click","text":"Login"},
{"type":"fill","label":"Email","value":"test@example.com"},
{"type":"fill","label":"Password","value":"secret"},
{"type":"click","role":"button","name":"Sign in"},
{"type":"assert","kind":"textVisible","text":"Dashboard"}
],
"returnArtifacts": ["screenshot","console","network"]
}' | jq .
# 3) Bei Fehler (ohne closeAfter): Diagnose-Bundle
curl -s "$BASE/browser/sessions/$SESSION/diagnostics?logTail=300" \
-H "Authorization: Bearer $KEY" | jq .
# 4) Manuell schließen (falls closeAfter nicht gesetzt)
curl -s -X DELETE "$BASE/browser/sessions/$SESSION" \
-H "Authorization: Bearer $KEY"Browser-Sessions & Lifecycle
Eine Session ist ein isolierter BrowserContext (eigene Cookies, Storage, Cache). Mehrere Sessions können parallel laufen – z. B. für verschiedene Test-Szenarien oder mehrere Agenten gleichzeitig.
Session erstellen — POST /browser/sessions
{
"target": "public",
"viewport": { "width": 1280, "height": 720 },
"timeoutMs": 30000,
"recordVideo": false
}target: "public"(Standard) — Live-URL des Projekts (Custom Domain oder*.up.kynxgate.de), identisch zum echten User-Traffictarget: "internal"—http://127.0.0.1:<PORT>direkt am Container (ohne Apache/SSL, nur vom Server)target: "https://deine-domain.de/pfad"— explizite URL, nur wenn der Host zum Projekt gehört (SSRF-Schutz)
POST /browser/sessions antwortet mit 409 project_offline, wenn kein Container läuft. Vorher GET /status prüfen.Session zuverlässig schließen — drei Wege (wichtig, damit keine Fenster offen bleiben):
- Empfohlen:
closeAfter: trueim actions-Body — schließt die Session automatisch nach dem Test (auch bei Fehlern) - Manuell:
DELETE /browser/sessions/{id} - Automatisch: Idle-TTL (~2 Min ohne Aktivität) oder Hard-TTL (max. 20 Min)
Zusätzlich synchronisiert ein Hintergrund-Reconciler die Datenbank alle 60s — „hängende“ ACTIVE-Einträge werden bereinigt, wenn der Browser-Service die Session nicht mehr kennt (z. B. nach Crash).
Aktionen & Assertions
Kern-Endpunkt: POST /browser/sessions/{id}/actions— führt ein Array von Aktionen sequentiell aus. Semantische Locators (agentenfreundlich) statt fragiler CSS-Selektoren:
{
"closeAfter": true,
"stopOnError": true,
"returnArtifacts": ["screenshot", "dom", "console", "network"],
"actions": [
{ "type": "goto", "path": "/login" },
{ "type": "fill", "label": "E-Mail", "value": "test@example.com" },
{ "type": "fill", "label": "Passwort", "value": "geheim" },
{ "type": "click", "role": "button", "name": "Anmelden" },
{ "type": "waitFor", "text": "Dashboard", "timeoutMs": 10000 },
{ "type": "assert", "kind": "urlContains", "value": "/dashboard" },
{ "type": "assert", "kind": "textVisible", "text": "Dashboard" },
{ "type": "screenshot", "fullPage": true },
{ "type": "getConsole", "tail": 30 },
{ "type": "getNetwork", "tail": 20, "failedOnly": true }
]
}| type | Wichtige Felder | Beschreibung |
|---|---|---|
| goto | url oder path | Seite öffnen (path relativ zur Session-baseUrl) |
| click | text, role+name, selector, label | Element anklicken |
| fill | label/value oder selector/value | Feld leeren und Text setzen |
| type | text (+ optional label/selector) | Zeichen für Zeichen tippen |
| press | key | Tastatur (Enter, Tab, Escape …) |
| hover | text, role, selector | Maus darüber |
| select | value, selector/label | Dropdown-Option wählen |
| check / uncheck | label, selector | Checkbox |
| scroll | to: top|bottom, by: N, selector | Scrollen |
| waitFor | selector, text, url, state, ms | Warten bis Bedingung |
| upload | selector, files[] | Datei-Upload (base64 oder path) |
| drag | from, to | Drag & Drop |
| screenshot | fullPage | PNG als base64 |
| getDom | maxChars | HTML der Seite |
| getA11y | — | Accessibility-Baum (für LLM-Agenten) |
| getConsole | tail | Browser-Console-Log |
| getNetwork | tail, failedOnly | Network-Events |
| getCookies / getStorage | — | Session-State lesen |
| assert | kind + … | Erwartung prüfen (siehe unten) |
Assertions (type: "assert") — Expectation → Action → Validation:
urlContains+value— URL enthält TexttextVisible+text— Text auf der Seite sichtbarselectorExists+selector— Element im DOMcookieExists+name— Cookie gesetzthttpOkForLastNav— letzte Navigation war HTTP 2xx/3xx
Fehlgeschlagene Assertions liefern 422 assertion_failed mit Details. Mit returnArtifacts kommen optional Screenshot, DOM, Console und Network am Ende des Requests mit.
GET .../diagnostics?logTail=300 kombiniert Browser-Console-Errors, fehlgeschlagene Requests und die letzten 300 Zeilen Docker-Runtime-Logs — ein Call für die komplette Fehleranalyse.Betrieb, Parallelität & Stabilität
Der Browser-Service läuft als eigener PM2-Prozess (kynxgate-browser) auf dem KynxGate-Server — getrennt vom Next.js-Prozess, damit Chromium-Last und Dev-Restarts sich nicht gegenseitig stören.
- Auto-Start:
npm run dev:restartstartet Proxy (9003) und Browser-Service (9005) automatisch mit - PM2:
npm run browser:restart— überlebt Terminal-Schließen und Server-Reboot (viapm2 save) - Health:
curl http://127.0.0.1:9005/_internal/health(nur lokal)
Limits & Parallelität (Standardeinstellungen auf dem Server):
| Limit | Wert | Env-Variable |
|---|---|---|
| Parallele Sessions (global) | 30 | BROWSER_MAX_SESSIONS |
| Parallele Sessions (pro Projekt) | 10 | BROWSER_MAX_SESSIONS_PER_PROJECT |
| API-Rate-Limit Browser | 60/min pro Key | — |
| API-Rate-Limit sonst | 120/min pro Key | — |
| Idle-Schließung | ~2 Min | BROWSER_SESSION_IDLE_TTL_MS |
| Max. Session-Dauer | 20 Min | BROWSER_SESSION_HARD_TTL_MS |
| Aktions-Timeout | 90s Hard-Cap | BROWSER_ACTION_HARD_CAP_MS |
closeAfter: true setzen. Bei parallelen Tests pro Projekt bis zu 10 Sessions — jede mit eigenem isolierten Cookie-Jar. Für Load-Tests lieber mehrere kurze Sessions mit closeAfter als eine lange offene Session.Beispiele
Status, Logs & Redeploy (curl):
# 1) API-Key als Umgebungsvariable ablegen (im Projekt unter "API Control" erstellen) export KYNX_API_KEY="kgp_xxxx_xxxxxxxxxxxxxxxxxxxx" export BASE="https://kynxgate.de/api/v1/projects/<workspaceId>/<projectName>" # 2) Status abfragen curl -s "$BASE/status" -H "Authorization: Bearer $KYNX_API_KEY" | jq # 3) Laufzeit-Logs (letzte 300 Zeilen) curl -s "$BASE/logs?source=runtime&tail=300" -H "Authorization: Bearer $KYNX_API_KEY" # 4) Redeploy auslösen curl -s -X POST "$BASE/redeploy" -H "Authorization: Bearer $KYNX_API_KEY" # 5) Live-Stream (Server-Sent Events): Status + neue Logzeilen curl -N "$BASE/logs/stream" -H "Authorization: Bearer $KYNX_API_KEY"
Autonomer Agent: Deploy → Browser-E2E-Test mit closeAfter:
// Vollständiger Agent-Loop: Deploy → warten → Browser-Test → bei Fehler Diagnostics
const KEY = process.env.KYNX_API_KEY
const BASE = "https://kynxgate.de/api/v1/projects/<workspaceId>/<projectName>"
const auth = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" }
async function waitOnline(maxMs = 300_000) {
const t0 = Date.now()
while (Date.now() - t0 < maxMs) {
const s = await fetch(`${BASE}/status`, { headers: auth }).then(r => r.json())
if (s.online && s.state === "online") return s
await new Promise(r => setTimeout(r, 5000))
}
throw new Error("Projekt wurde nicht online")
}
// Nach Redeploy warten
await fetch(`${BASE}/redeploy`, { method: "POST", headers: auth })
await waitOnline()
// Browser-Session: Test + sofort schließen (closeAfter)
const session = await fetch(`${BASE}/browser/sessions`, {
method: "POST", headers: auth, body: JSON.stringify({ target: "public" }),
}).then(r => r.json())
const actions = await fetch(`${BASE}/browser/sessions/${session.sessionId}/actions`, {
method: "POST",
headers: auth,
body: JSON.stringify({
closeAfter: true,
actions: [
{ type: "goto", path: "/" },
{ type: "assert", kind: "httpOkForLastNav" },
{ type: "click", text: "Registrieren" },
{ type: "fill", label: "E-Mail", value: "agent@test.de" },
{ type: "assert", kind: "textVisible", text: "Willkommen" },
],
returnArtifacts: ["screenshot", "console", "network"],
}),
}).then(r => r.json())
const failed = actions.results?.some(r => !r.ok)
if (failed) {
const diag = await fetch(
`${BASE}/browser/sessions/${session.sessionId}/diagnostics?logTail=200`,
{ headers: auth }
).then(r => r.json())
console.error("Browser-Fehler:", diag.browser)
console.error("Container-Logs:", diag.logs?.runtime?.slice(-2000))
// → Code fixen, redeploy, wiederholen
}Klassischer Agent bei Build-Fehler:
const KEY = process.env.KYNX_API_KEY
const BASE = "https://kynxgate.de/api/v1/projects/<workspaceId>/<projectName>"
const auth = { Authorization: `Bearer ${KEY}` }
const status = await fetch(`${BASE}/status`, { headers: auth }).then(r => r.json())
if (status.state === "failed") {
// Build-Logs holen und automatisch neu deployen
const logs = await fetch(`${BASE}/logs?source=build`, { headers: auth }).then(r => r.json())
console.error("Build fehlgeschlagen:\n", logs.log)
await fetch(`${BASE}/redeploy`, { method: "POST", headers: auth })
}Dasselbe in Python:
import os, requests
KEY = os.environ["KYNX_API_KEY"]
BASE = "https://kynxgate.de/api/v1/projects/<workspaceId>/<projectName>"
auth = {"Authorization": f"Bearer {KEY}"}
status = requests.get(f"{BASE}/status", headers=auth).json()
print(status["state"], status["online"])
if status["state"] == "failed":
logs = requests.get(f"{BASE}/logs", params={"source": "build"}, headers=auth).json()
print(logs["log"])
requests.post(f"{BASE}/redeploy", headers=auth)Live-Logs streamen (SSE):
// Live-Stream in Node.js mitlesen (SSE über fetch)
const res = await fetch(`${BASE}/logs/stream`, {
headers: { Authorization: `Bearer ${KEY}` },
})
const reader = res.body.getReader()
const dec = new TextDecoder()
for (;;) {
const { value, done } = await reader.read()
if (done) break
process.stdout.write(dec.decode(value)) // event: log / deployment / ping …
}ready, deployment, log und ping. Da Browser-EventSource keine eigenen Header setzen kann, nutze zum Streamen fetch/curl -N mit dem Authorization-Header (wie oben).Fehlercodes & Rate-Limits
| Status | Code | Bedeutung |
|---|---|---|
| 401 | missing_api_key / invalid_api_key | Key fehlt oder ist ungültig |
| 403 | insufficient_scope | Key hat die nötige Berechtigung nicht (z. B. browser:write) |
| 403 | revoked / expired | Key widerrufen oder abgelaufen |
| 403 | project_mismatch | Key gehört zu einem anderen Projekt |
| 404 | not_found | Ressource nicht gefunden (Deployment, Browser-Session) |
| 409 | project_offline | Browser-Test: Projekt/Container läuft nicht |
| 422 | assertion_failed | Browser-Assertion fehlgeschlagen (Details im Body) |
| 429 | rate_limited / session_cap | Zu viele Anfragen oder zu viele parallele Sessions |
| 500 | action_failed / internal_error | Aktion oder Serverfehler |
| 503 | docker_disabled | Docker nicht aktiv — Browser-Tests nicht verfügbar |
429 mit Retry-After. Fehler immer als JSON { "error": "...", "message": "..." }.browser:read + browser:write) und leg los.