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
| Header | Wert |
|---|---|
Authorization | Bearer <jwt> auf allen /api/*-Routen außer /api/auth/* und /api/server-info (sowie /health) |
Content-Type | application/json für POST/PUT mit JSON-Body. application/octet-stream für binäre Uploads (build files). |
Accept | application/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 Benutzer429: 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 odertoken_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 Repo404: 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 Name403: fehlende Capabilitycreate_repos409: 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 nicht403: fehlende Capabilitycheckinoder keine Write-Permission auf einer der Dateien409: 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 usernamesince(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 Felderlock_holderundlock_acquired_atsind gefüllt)PERMISSION_DENIED: keine Write-Permission auf diesem PfadINVALID_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.
| Route | Beschreibung |
|---|---|
GET /api/tasks/{repo_id}/board | Vollständiges Board: Spalten + Karten |
POST /api/tasks/{repo_id}/tasks | Erstellt eine Karte |
PUT/DELETE /api/tasks/{repo_id}/tasks/{task_id} | Aktualisiert / löscht eine Karte |
PUT /api/tasks/{repo_id}/tasks/{task_id}/assignees | Weist einer Karte Benutzer zu |
GET /api/tasks/{repo_id}/assignable-users | Liste der zuweisbaren Benutzer |
POST /api/tasks/{repo_id}/columns | Konfiguriert 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 Hash413: 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).
| Route | Capability | Beschreibung |
|---|---|---|
GET/POST /api/admin/users | manage_users | Listet / erstellt Benutzer |
PUT/DELETE /api/admin/users/{id} | manage_users | Aktualisiert / löscht |
POST /api/admin/users/{id}/reset-password | manage_users | Setzt das Passwort zurück, erhöht token_version |
GET/POST /api/admin/groups | manage_users | Gruppenverwaltung |
GET/POST /api/admin/permissions/{repo_id} | manage_permissions | Glob-Permission-Regeln pro Pfad |
GET/POST /api/admin/repositories/{repo_id}/rules | manage_rules | Validierungsregeln vor dem Checkin |
GET/POST /api/admin/repositories/{repo_id}/webhooks | manage_rules | Discord / Slack / Teams / custom |
GET /api/admin/audit | view_all_activity | Gefiltertes und paginiertes Audit-Log |
GET /api/admin/stats | view_all_activity | Storage Metrics, Dedup Ratio, Wachstum |
GET /api/admin/repositories/{repo_id}/gc/preview | admin | Vorschau des GC ohne Ausführung |
POST /api/admin/repositories/{repo_id}/gc | admin | Startet die Garbage Collection |
POST /api/admin/locks/{id}/force-release | force_unlock | Erzwingt 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").
| HTTP | Bedeutung | Wann |
|---|---|---|
| 400 | Bad request | Ungültiger Parameter, fehlerhaftes JSON, Body zu groß (vor dem harten 413-Limit) |
| 401 | Not authenticated | Token fehlt, abgelaufen, ungültige Signatur oder token_version-Mismatch |
| 403 | Permission denied | Fehlende Capability oder keine Permission auf dem Pfad / Repo |
| 404 | Resource not found | Repo / Datei / Commit / Benutzer existiert nicht oder ist nicht zugänglich |
| 409 | Conflict | Sperre bereits gehalten, gleichzeitiger Commit, Unique-Constraint verletzt |
| 413 | Payload too large | Body über dem Limit (1 GB auf /files, 8 GB auf /builds/upload) |
| 429 | Too many requests | Nur auf /auth/login (Rate Limiting pro Benutzer) |
| 500 | Internal server error | DB-Fehler, IO-Fehler. Serverseitig geloggt; fügen Sie den Timestamp bei, wenn Sie den Bug melden. |
| 503 | Service unavailable | Datenbank nicht erreichbar (Health Check) oder Migration im Gange |