uVersion
Deutsch
Herunterladen →

Wiki

REST API

Vollständige Referenz der HTTP-Endpunkte des uVersion-Servers: auth, repos, files, locks, comments, watchlist, modification requests, builds, admin.

Der uVersion-Server stellt eine per JWT authentifizierte JSON-HTTP-API bereit. Diese Seite dokumentiert sämtliche Endpunkte, die vom Desktop-Client, den Editor-Plugins und der uversion-CLI genutzt werden. Sie können sie direkt aufrufen, um uVersion in Ihr internes Tooling (eigenes Dashboard, Audit-Skripte, Webhooks usw.) zu integrieren.

Konventionen

Basis-URL

Alle dokumentierten Pfade sind relativ zur URL Ihrer Instanz. Die Beispiele verwenden https://uversion.mygamestudio.com. Ersetzen Sie sie durch Ihre eigene.

Erforderliche Header

HeaderWert
AuthorizationBearer <jwt> auf allen /api/*-Routen außer /api/auth/* und /api/server-info (sowie /health)
Content-Typeapplication/json für POST/PUT mit JSON-Body. application/octet-stream für binäre Uploads (build files).
Acceptapplication/json empfohlen (der Server liefert standardmäßig JSON)

Einheitliches Antwortformat

Alle Routen antworten mit folgendem Umschlag:

{
  "success": true,
  "data": { /* payload */ }
}

Im Fehlerfall:

{
  "success": false,
  "error": "Permission denied: capability 'manage_users' required"
}

Das Feld success ist immer vorhanden und gibt an, ob die Anfrage erfolgreich war. Das Feld data ist bei Erfolg vorhanden. Das Feld error ist bei einem Fehler vorhanden. Niemals beide zusammen.

Pagination

Routen, die potenziell lange Listen zurückgeben, akzeptieren limit und offset als Query-Parameter (?limit=20&offset=40). Die Antwort enthält total und has_more, um die Iteration zu erleichtern. Maximales limit: 100.

Rate Limiting

Das globale Rate Limiting wurde entfernt (siehe CLAUDE.md, Abschnitt Security). Nur /api/auth/login ist rate-limitiert: 5 fehlgeschlagene Versuche pro username alle 15 Minuten. Gültige Versuche zählen nicht.

Token-Versionierung

Jeder JWT enthält ein Feld tv (token version), das users.token_version in der DB entspricht. Die Auth-Middleware prüft diesen Wert bei jeder Anfrage. Wenn der Benutzer POST /api/auth/logout ausführt, ein Admin sein Passwort zurücksetzt oder ein Admin sein Konto deaktiviert, wird token_version erhöht, wodurch sofort alle bestehenden Tokens dieses Benutzers auf allen seinen Maschinen ungültig werden.

Curl

Um die API aus einem Terminal aufzurufen:

TOKEN=$(curl -s -X POST https://uversion.mygamestudio.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"secret"}' | jq -r '.data.token')

curl -H "Authorization: Bearer $TOKEN" \
  https://uversion.mygamestudio.com/api/repositories

Authentifizierung

POST /api/auth/login

Authentifiziert einen Benutzer und gibt einen 30 Tage gültigen JWT zurück.

Body:

{
  "username": "alice",
  "password": "secret"
}

Response 200:

{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expires_at": "2026-06-13T14:30:00Z",
    "user": {
      "id": 12,
      "username": "alice",
      "email": "alice@mygamestudio.com",
      "role": "lead",
      "is_active": true
    }
  }
}

Fehler:

  • 401: ungültige Anmeldedaten oder inaktiver Benutzer
  • 429: 5 fehlgeschlagene Versuche für diesen username im 15-Minuten-Fenster erreicht

Der Server führt immer argon2id::verify_password aus (gegen einen Dummy-Hash, falls der Benutzer nicht existiert), um Timing-Angriffe zu neutralisieren. Die Antwort benötigt dieselbe Zeit, egal ob der Benutzer existiert oder nicht.

POST /api/auth/refresh

Erneuert den JWT, ohne das Passwort erneut einzugeben.

Header: Authorization: Bearer <current_token>. Kein Body.

Response 200: identisch zu /login, mit um 30 Tage verschobenem Ablauf und einem neuen Token.

Fehler:

  • 401: aktuelles Token ungültig, abgelaufen oder token_version-Mismatch

POST /api/auth/logout

Macht alle Tokens des aktuellen Benutzers (auf allen seinen Maschinen) ungültig, indem users.token_version erhöht wird. Der Benutzer muss sich überall neu anmelden.

Response 200: { "success": true }.

POST /api/auth/validate

Prüft, ob das Token noch gültig ist. Für Tokens, die in weniger als 7 Tagen ablaufen, fügt der Server ein refreshed_token in die Antwort ein (Auto-Extend). Der Client muss es anstelle des alten persistieren.

Response 200:

{
  "success": true,
  "data": {
    "valid": true,
    "user_id": 12,
    "expires_at": "2026-06-13T14:30:00Z",
    "refreshed_token": "eyJhbGciOiJIUzI1NiIs..."
  }
}

POST /api/auth/register

Erstellt ein Benutzerkonto (öffentliche Route, ohne Authentifizierung).

POST /api/auth/change-password

Ändert das Passwort des aktuellen Benutzers. Erhöht token_version, wodurch alle alten Tokens ungültig werden.

Repositories

GET /api/repositories

Listet die für den aktuellen Benutzer zugänglichen Repositories auf (gefiltert über die Permissions-Tabelle). Ein Benutzer ohne Permission-Pattern auf einem Repo sieht es nicht.

Query-Parameter: limit, offset.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "hero-rpg",
      "owner": "acme",
      "description": "Main RPG project",
      "current_revision": "7f3a9b1c2d...",
      "file_count": 8432,
      "size_bytes": 14211938405,
      "created_at": "2026-01-15T10:00:00Z"
    },
    {
      "id": 2,
      "name": "shared-assets",
      "owner": "acme",
      "description": "Shared asset library",
      "current_revision": "3a4b5c6d...",
      "file_count": 412,
      "size_bytes": 980000000,
      "created_at": "2026-02-01T09:00:00Z"
    }
  ]
}

GET /api/repositories/{repo_id}

Detail eines Repositories, einschließlich der aggregierten Statistiken.

Response 200:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "hero-rpg",
    "owner": "acme",
    "description": "Main RPG project",
    "current_revision": "7f3a9b1c2d...",
    "file_count": 8432,
    "size_bytes": 14211938405,
    "dedup_ratio": 4.2,
    "created_at": "2026-01-15T10:00:00Z",
    "last_commit_at": "2026-05-15T08:30:00Z"
  }
}

Fehler:

  • 403: kein Zugriff auf dieses Repo
  • 404: Repo existiert nicht

POST /api/repositories

Erstellt ein neues Repository. Capability create_repos erforderlich (standardmäßig admin).

Body:

{
  "name": "new-project",
  "description": "Optional description"
}

Validierung:

  • name: 1 bis 64 Zeichen, alphanumerisch + Bindestriche + Unterstriche. Eindeutig pro owner.
  • description: 0 bis 1024 Zeichen. Optional.

Fehler:

  • 400: ungültiger oder zu langer Name
  • 403: fehlende Capability create_repos
  • 409: ein Repo mit diesem Namen existiert bereits für diesen owner

Dateien

POST /api/files/{repo_id}/upload-chunks

Lädt einen Batch binärer Chunks hoch. Identische Chunks (per SHA-256) werden automatisch dedupliziert. Der Server speichert einen bereits vorhandenen Chunk nicht erneut. Die Antwort liefert für jeden Chunk seinen Hash + Größe + komprimierte Größe, zur Verwendung im folgenden POST /commit.

Body:

{
  "chunks": [
    { "data": "<base64-encoded chunk bytes>" },
    { "data": "<base64-encoded chunk bytes>" }
  ]
}

Response 200:

{
  "success": true,
  "data": {
    "chunks": [
      { "hash": "abc123...", "size": 1048576, "compressed_size": 423152 },
      { "hash": "def456...", "size": 2097152, "compressed_size": 891204 }
    ]
  }
}

Limits: Body max. 1 GB (konfigurierbar über security.max_body_size_files). Für größere Dateien in mehrere Batches aufteilen.

POST /api/files/{repo_id}/commit

Erstellt einen atomaren Commit mit einer Liste von Dateien und ihren Chunks. Entweder alle Dateien gehen durch oder keine.

Body:

{
  "message": "Updated main level + hero pose pass",
  "files": [
    {
      "path": "Content/Maps/MainLevel.umap",
      "action": "modified",
      "chunks": ["abc123...", "def456..."]
    },
    {
      "path": "Content/Characters/NewVillain.uasset",
      "action": "added",
      "chunks": ["fed789..."]
    },
    {
      "path": "Content/OldAsset.uasset",
      "action": "deleted"
    }
  ]
}

Mögliche Aktionen: added, modified, deleted. Für added und modified enthält das Array chunks die von /upload-chunks zurückgegebenen Hashes. Für deleted chunks weglassen.

Response 200:

{
  "success": true,
  "data": {
    "commit_hash": "7f3a9b1c2d3e4f...",
    "files_changed": 3,
    "bytes_uploaded": 88080384,
    "bytes_deduped": 4194304,
    "revision": 47
  }
}

Fehler:

  • 400: leere Nachricht, Datei ohne Aktion, referenzierte Chunks existieren nicht
  • 403: fehlende Capability checkin oder keine Write-Permission auf einer der Dateien
  • 409: Sperre bereits von einem anderen Benutzer auf einer geänderten Datei gehalten oder gleichzeitiger Commit (Race Condition auf derselben Datei)

GET /api/files/{repo_id}/snapshot

Gibt den vollständigen Zustand des Repositories zur aktuellen Revision zurück: alle Dateien, ihre Revisionen und ihre Chunks. Vom Client für Clone- und erzwungene Sync-Operationen verwendet.

Query-Parameter:

  • revision (optional): Snapshot zu einer vergangenen Revision. Standard: HEAD.

Response 200:

{
  "success": true,
  "data": {
    "revision": 47,
    "files": [
      {
        "path": "Content/Maps/MainLevel.umap",
        "revision": 12,
        "size_bytes": 84934656,
        "chunks": ["abc123...", "def456..."]
      }
    ]
  }
}

GET /api/files/{repo_id}/content

Lädt den Inhalt einer Datei zu einer gegebenen Revision herunter (der Server setzt die Chunks wieder zusammen). Von Clone und Sync verwendet.

Query-Parameter:

  • path: Dateipfad (relativ zum Repo-Root)
  • revision (optional): Revisionsnummer. Standard: neueste.

Response 200: der binäre Inhalt der Datei.

GET /api/files/{repo_id}/history

Paginierte Historie der Commits des Repositories.

Query-Parameter:

  • limit (1 bis 100, Standard 20)
  • offset (Standard 0)
  • path (optional): Filter nach Dateipfad (Commits, die diese Datei geändert haben)
  • author (optional): Filter nach username
  • since (optional): ISO 8601 timestamp

Response 200:

{
  "success": true,
  "data": {
    "commits": [
      {
        "hash": "7f3a9b1c...",
        "author": "alice",
        "author_id": 12,
        "date": "2026-05-15T08:30:00Z",
        "message": "Fixed lighting in main level",
        "files_changed": 1,
        "bytes_uploaded": 84934656
      }
    ],
    "total": 423,
    "limit": 20,
    "offset": 0,
    "has_more": true
  }
}

Inkrementeller Sync

Endpunkte, die der Desktop-Client für effiziente Synchronisation verwendet:

  • GET /api/files/{repo_id}/sync: seit einer Revision geänderte Dateien, für einen Delta-Sync.
  • GET /api/files/{repo_id}/deletions: serverseitig gelöschte Dateien, um Löschungen lokal zu propagieren.
  • GET /api/files/{repo_id}/list: Liste der Dateien des Repos.
  • POST /api/files/{repo_id}/checkout: erwirbt die Sperren und bereitet die Bearbeitung vor.

Sperren

POST /api/locks/{repo_id}/acquire

Erwirbt Sperren auf einer Liste von Pfaden. Die Erwerbungen sind unabhängig: die Liste acquired enthält die Erfolge, die Liste failed enthält die Fehlschläge mit ihrem Grund.

Body:

{
  "paths": [
    "Content/Maps/MainLevel.umap",
    "Content/Characters/Hero.uasset"
  ]
}

Response 200:

{
  "success": true,
  "data": {
    "acquired": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "file_id": 12345,
        "file_path": "Content/Maps/MainLevel.umap",
        "user_id": 12,
        "username": "alice",
        "acquired_at": "2026-05-15T14:30:00Z",
        "last_heartbeat_at": "2026-05-15T14:30:00Z",
        "expires_at": "2026-05-15T15:30:00Z"
      }
    ],
    "failed": [
      {
        "path": "Content/Characters/Hero.uasset",
        "reason": "ALREADY_LOCKED",
        "lock_holder": "bob",
        "lock_acquired_at": "2026-05-15T13:00:00Z"
      }
    ]
  }
}

Sperren laufen nach 60 Minuten ohne Heartbeat ab. Das Feld expires_at spiegelt diese Frist wider. Rufen Sie /heartbeat regelmäßig auf, um die Sperre zu erneuern, oder /release, um sie freizugeben.

Gründe für failed:

  • ALREADY_LOCKED: ein anderer Benutzer hält die Sperre (die Felder lock_holder und lock_acquired_at sind gefüllt)
  • PERMISSION_DENIED: keine Write-Permission auf diesem Pfad
  • INVALID_PATH: fehlerhafter Pfad (absolut, enthält .. usw.)

POST /api/locks/{repo_id}/release

Gibt Sperren frei, die Sie besitzen. Body:

{
  "paths": ["Content/Maps/MainLevel.umap"],
  "force": false
}

force: true erfordert die Capability force_unlock. Die Aktion wird auditiert.

POST /api/locks/{repo_id}/heartbeat

Aktualisiert last_heartbeat_at auf Ihren Sperren. Empfohlen alle 5 Minuten für lang laufende CI-Jobs, die möchten, dass Admins die Aktivität sehen.

GET /api/locks/{repo_id}/status

Listet alle aktiven Sperren des Repositories auf, joined auf users und files, um die Namen direkt zurückzugeben.

Query-Parameter: user (Filter nach username), limit, offset.

Response 200:

{
  "success": true,
  "data": {
    "locks": [
      {
        "file_path": "Content/Maps/MainLevel.umap",
        "user_id": 12,
        "username": "alice",
        "acquired_at": "2026-05-15T14:30:00Z",
        "last_heartbeat_at": "2026-05-15T16:00:00Z"
      }
    ],
    "total": 1
  }
}

Kommentare & Reviews

GET /api/comments/{repo_id}/comments?commit_hash=<hash>

Listet die Kommentare eines Commits auf, als Threads (Parent → Kinder).

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 42,
      "commit_hash": "7f3a9b1c...",
      "file_path": "Content/Maps/MainLevel.umap",
      "author_id": 12,
      "author_username": "alice",
      "body": "LGTM, ship it",
      "parent_id": null,
      "created_at": "2026-05-15T15:00:00Z",
      "replies": [
        {
          "id": 43,
          "author_username": "bob",
          "body": "Thanks!",
          "parent_id": 42,
          "created_at": "2026-05-15T15:05:00Z"
        }
      ]
    }
  ]
}

POST /api/comments/{repo_id}/comments

Erstellt einen Kommentar. file_path ist optional (Commit-Kommentar vs. Datei-Kommentar). parent_id, um auf einen bestehenden Thread zu antworten.

Body:

{
  "commit_hash": "7f3a9b1c...",
  "file_path": "Content/Maps/MainLevel.umap",
  "body": "LGTM, ship it",
  "parent_id": null
}

POST /api/comments/{repo_id}/reviews

Reicht ein Review zu einem Commit ein. Capability approve_changes erforderlich.

Body:

{
  "commit_hash": "7f3a9b1c...",
  "status": "approved",
  "comment": "Lighting looks great, approving"
}

Akzeptierter status: approved, changes_requested, pending.

Beobachtungsliste

GET /api/watchlist/{repo_id}/watchlist

Listet die Watch-Patterns des aktuellen Benutzers auf diesem Repository auf.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "user_id": 12,
      "pattern": "Content/Characters/Hero/**",
      "notify_on": ["commit", "lock"],
      "created_at": "2026-04-10T10:00:00Z"
    }
  ]
}

POST /api/watchlist/{repo_id}/watchlist

Fügt ein Watch-Pattern hinzu. Body:

{
  "pattern": "Content/Characters/Hero/**",
  "notify_on": ["commit", "lock"]
}

Mögliche Ereignisse: commit (ein Commit hat eine passende Datei geändert), lock (eine Sperre wurde erworben), review (ein Review wurde gepostet).

DELETE /api/watchlist/{repo_id}/watchlist/{watch_id}

Entfernt ein Watch-Pattern. Gibt { "success": true } zurück.

Produktions-Board (Tasks)

Die alte modification-requests-API wurde entfernt: das Produktions-Board absorbiert sie (Anfragen werden zu Karten, origin='request'). Die Endpunkte sind unter /api/tasks/{repo_id} verschachtelt.

RouteBeschreibung
GET /api/tasks/{repo_id}/boardVollständiges Board: Spalten + Karten
POST /api/tasks/{repo_id}/tasksErstellt eine Karte
PUT/DELETE /api/tasks/{repo_id}/tasks/{task_id}Aktualisiert / löscht eine Karte
PUT /api/tasks/{repo_id}/tasks/{task_id}/assigneesWeist einer Karte Benutzer zu
GET /api/tasks/{repo_id}/assignable-usersListe der zuweisbaren Benutzer
POST /api/tasks/{repo_id}/columnsKonfiguriert die Spalten des Boards

Ebenfalls verfügbare Unterrouten: Kartenkommentare, Links zu Assets und Commits sowie Anhänge (mit Titelbild). Die Konfiguration der Spalten erfordert die Capability manage_board (admin / lead).

Builds

POST /api/builds/{repo_id}/upload?hash=<sha256>

Lädt eine Build-Datei nach <storage_path>/builds/<hash> hoch. Der Server verifiziert den im Query-Parameter angegebenen SHA-256 gegen den empfangenen Inhalt. Wenn der Hash serverseitig bereits existiert, gibt er sofort 200 zurück, ohne erneut zu kopieren (Dedup im Build-Storage).

Header: Content-Type: application/octet-stream.

Body: rohe Binärdaten (kein JSON-Wrapping).

Limits: 8 GB pro Datei.

Fehler:

  • 400: hash-Query-Parameter fehlt oder ist fehlerhaft, oder berechneter SHA-256 != angegebener Hash
  • 413: Datei > 8 GB

POST /api/builds/{repo_id}/publish

Registriert das Manifest eines Builds, nachdem alle seine Dateien hochgeladen wurden. Capability publish_builds erforderlich.

Body:

{
  "version": "0.1.5-nightly",
  "config": "Development",
  "platform": "Win64",
  "executable_path": "HeroRPG/Binaries/Win64/HeroRPG.exe",
  "release_notes": "Nightly build of main branch, 2026-05-15",
  "files": [
    { "path": "HeroRPG.exe",                "hash": "abc123...", "size_bytes": 12345678 },
    { "path": "HeroRPG/Content/Paks/pak0.pak", "hash": "def456...", "size_bytes": 2147483648 }
  ]
}

GET /api/builds/{repo_id}

Listet die veröffentlichten Builds auf. Capability download_builds erforderlich, um die Download-Links zu sehen.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 5,
      "version": "0.1.5-nightly",
      "config": "Development",
      "platform": "Win64",
      "published_by": "ci-nightly",
      "published_at": "2026-05-15T03:00:00Z",
      "size_bytes": 2159829326,
      "file_count": 142,
      "release_notes": "Nightly build of main branch, 2026-05-15"
    }
  ]
}

GET /api/builds/{repo_id}/{build_id}/file

Lädt eine Datei eines Builds herunter. Capability download_builds erforderlich. Das Manifest eines Builds ist über GET /api/builds/{repo_id}/{build_id}/manifest verfügbar.

Admin

Alle /api/admin/*-Routen erfordern, dass der Benutzer mindestens eine Admin-Capability besitzt (je nach Endpunkt: manage_users, manage_permissions, manage_rules usw.). Kurze Dokumentation unten. Die meisten folgen dem Standard-REST-Muster (GET/POST/PUT/DELETE).

RouteCapabilityBeschreibung
GET/POST /api/admin/usersmanage_usersListet / erstellt Benutzer
PUT/DELETE /api/admin/users/{id}manage_usersAktualisiert / löscht
POST /api/admin/users/{id}/reset-passwordmanage_usersSetzt das Passwort zurück, erhöht token_version
GET/POST /api/admin/groupsmanage_usersGruppenverwaltung
GET/POST /api/admin/permissions/{repo_id}manage_permissionsGlob-Permission-Regeln pro Pfad
GET/POST /api/admin/repositories/{repo_id}/rulesmanage_rulesValidierungsregeln vor dem Checkin
GET/POST /api/admin/repositories/{repo_id}/webhooksmanage_rulesDiscord / Slack / Teams / custom
GET /api/admin/auditview_all_activityGefiltertes und paginiertes Audit-Log
GET /api/admin/statsview_all_activityStorage Metrics, Dedup Ratio, Wachstum
GET /api/admin/repositories/{repo_id}/gc/previewadminVorschau des GC ohne Ausführung
POST /api/admin/repositories/{repo_id}/gcadminStartet die Garbage Collection
POST /api/admin/locks/{id}/force-releaseforce_unlockErzwingt das Entsperren einer fremden Sperre, auditiert

Health

GET /health

Nicht authentifizierter Endpunkt, der 200 OK zurückgibt, wenn der Server die Datenbank erreichen kann. Für Health Checks von Load Balancern oder Monitoring verwendet (Prometheus blackbox, Datadog synthetic usw.).

Response 200: OK (text/plain).

Response 503: wenn die Datenbank nicht erreichbar ist.

GET /api/server-info

Öffentlicher, nicht authentifizierter Endpunkt, der die Server-Metadaten (Version usw.) zurückgibt. Wie /health ist er nicht durch die Auth-Middleware geschützt.

Fehlerformat

Im Fehlerfall lautet die Antwort { "success": false, "error": "..." } mit einem passenden HTTP-Status. Das Feld error ist eine menschenlesbare Nachricht. Für Clients, die stabile maschinenlesbare Codes benötigen, parsen Sie das Präfix (z. B. beginnt "Permission denied: ..." immer mit "Permission denied").

HTTPBedeutungWann
400Bad requestUngültiger Parameter, fehlerhaftes JSON, Body zu groß (vor dem harten 413-Limit)
401Not authenticatedToken fehlt, abgelaufen, ungültige Signatur oder token_version-Mismatch
403Permission deniedFehlende Capability oder keine Permission auf dem Pfad / Repo
404Resource not foundRepo / Datei / Commit / Benutzer existiert nicht oder ist nicht zugänglich
409ConflictSperre bereits gehalten, gleichzeitiger Commit, Unique-Constraint verletzt
413Payload too largeBody über dem Limit (1 GB auf /files, 8 GB auf /builds/upload)
429Too many requestsNur auf /auth/login (Rate Limiting pro Benutzer)
500Internal server errorDB-Fehler, IO-Fehler. Serverseitig geloggt; fügen Sie den Timestamp bei, wenn Sie den Bug melden.
503Service unavailableDatenbank nicht erreichbar (Health Check) oder Migration im Gange