Wiki
REST API
uVersion サーバーの HTTP エンドポイントの完全なリファレンス:auth、repos、files、locks、comments、watchlist、modification requests、builds、admin。
uVersion サーバーは JWT で認証された JSON HTTP API を公開します。このページでは、デスクトップクライアント、
エディタープラグイン、および uversion CLI が使用するすべてのエンドポイントを説明します。
これらを直接呼び出して、uVersion を社内ツール(カスタムダッシュボード、監査スクリプト、webhook など)に
統合できます。
規約
ベース URL
ドキュメント化されたすべてのパスは、お使いのインスタンスの URL からの相対パスです。例では
https://uversion.mygamestudio.com を使用しています。ご自身の URL に置き換えてください。
必須ヘッダー
| ヘッダー | 値 |
|---|---|
Authorization | /api/auth/* と /api/server-info(および /health)を除くすべての /api/* ルートで Bearer <jwt> |
Content-Type | JSON ボディを伴う POST/PUT には application/json。バイナリアップロード(build files)には application/octet-stream。 |
Accept | application/json を推奨(サーバーはデフォルトで JSON を返します) |
統一されたレスポンス形式
すべてのルートは次のエンベロープで応答します:
{
"success": true,
"data": { /* payload */ }
}
エラー時:
{
"success": false,
"error": "Permission denied: capability 'manage_users' required"
}
success フィールドは常に存在し、リクエストが成功したかどうかを示します。
成功時には data フィールドが存在します。失敗時には error フィールドが存在します。
両方が同時に存在することはありません。
ページネーション
長くなる可能性のあるリストを返すルートは、クエリパラメータとして limit と offset
を受け付けます(?limit=20&offset=40)。レスポンスには反復処理を容易にするための
total と has_more が含まれます。最大 limit:100。
レート制限
グローバルなレート制限は削除されました(CLAUDE.md の Security セクションを参照)。/api/auth/login
のみがレート制限されます:username ごとに 15 分間で 5 回の失敗試行。有効な試行は
カウントされません。
トークンバージョニング
各 JWT には、DB の users.token_version に対応する tv フィールド(token version)が含まれます。
auth ミドルウェアはリクエストごとにこの値をチェックします。ユーザーが POST /api/auth/logout を実行したとき、
管理者がそのパスワードをリセットしたとき、または管理者がそのアカウントを無効化したときに token_version がインクリメントされ、
そのユーザーのすべてのマシン上の既存のすべてのトークンが即座に無効化されます。
Curl
ターミナルから API を呼び出すには:
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
認証
POST /api/auth/login
ユーザーを認証し、30 日間有効な JWT を返します。
ボディ:
{
"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
}
}
}
エラー:
401:無効な認証情報または無効化されたユーザー429:この username で 15 分間のウィンドウ内に 5 回の失敗試行に達した
サーバーはタイミング攻撃を無効化するため、常に argon2id::verify_password(ユーザーが存在しない場合はダミーハッシュに対して)を実行します。
ユーザーが存在するかどうかにかかわらず、レスポンスにかかる時間は同じです。
POST /api/auth/refresh
パスワードを再度入力せずに JWT を更新します。
ヘッダー: Authorization: Bearer <current_token>。ボディなし。
Response 200: /login と同一で、有効期限が 30 日先に延長され、新しいトークンが返されます。
エラー:
401:現在のトークンが無効、期限切れ、またはtoken_versionの不一致
POST /api/auth/logout
users.token_version をインクリメントすることで、現在のユーザーのすべてのトークン(すべてのマシン上)を無効化します。
ユーザーはすべての場所で再ログインする必要があります。
Response 200: { "success": true }。
POST /api/auth/validate
トークンがまだ有効であることを確認します。期限まで 7 日未満のトークンについては、サーバーはレスポンスに
refreshed_token を含めます(自動延長)。クライアントは古いものの代わりにこれを永続化する必要があります。
Response 200:
{
"success": true,
"data": {
"valid": true,
"user_id": 12,
"expires_at": "2026-06-13T14:30:00Z",
"refreshed_token": "eyJhbGciOiJIUzI1NiIs..."
}
}
POST /api/auth/register
ユーザーアカウントを作成します(認証不要のパブリックルート)。
POST /api/auth/change-password
現在のユーザーのパスワードを変更します。token_version をインクリメントし、すべての古いトークンを無効化します。
リポジトリ
GET /api/repositories
現在のユーザーがアクセスできるリポジトリを一覧表示します(permissions テーブルでフィルタリング)。 リポジトリに対する permission パターンを持たないユーザーには表示されません。
クエリパラメータ: 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}
集計された統計を含む、リポジトリの詳細。
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"
}
}
エラー:
403:このリポジトリへのアクセス権がない404:リポジトリが存在しない
POST /api/repositories
新しいリポジトリを作成します。create_repos capability が必要です(デフォルトでは admin)。
ボディ:
{
"name": "new-project",
"description": "Optional description"
}
バリデーション:
- name:1〜64 文字、英数字 + ハイフン + アンダースコア。owner ごとに一意。
- description:0〜1024 文字。任意。
エラー:
400:無効または長すぎる名前403:create_reposcapability が不足409:この owner にはこの名前のリポジトリが既に存在する
ファイル
POST /api/files/{repo_id}/upload-chunks
バイナリチャンクのバッチをアップロードします。同一のチャンク(SHA-256 による)は自動的に重複排除されます。
サーバーは既に存在するチャンクを再保存しません。レスポンスは各チャンクについてハッシュ + サイズ + 圧縮後サイズを返し、
続く POST /commit で使用します。
ボディ:
{
"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 }
]
}
}
制限: ボディ最大 1 GB(security.max_body_size_files で設定可能)。より大きなファイルの場合は複数のバッチに分割してください。
POST /api/files/{repo_id}/commit
ファイルのリストとそのチャンクでアトミックなコミットを作成します。すべてのファイルが成功するか、まったく成功しないかのどちらかです。
ボディ:
{
"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"
}
]
}
可能なアクション:added、modified、deleted。
added と modified の場合、chunks 配列には /upload-chunks が返したハッシュが入ります。
deleted の場合は chunks を省略します。
Response 200:
{
"success": true,
"data": {
"commit_hash": "7f3a9b1c2d3e4f...",
"files_changed": 3,
"bytes_uploaded": 88080384,
"bytes_deduped": 4194304,
"revision": 47
}
}
エラー:
400:メッセージが空、アクションのないファイル、存在しないチャンクを参照403:checkincapability がない、またはいずれかのファイルに対する write permission がない409:変更されたファイルに対して別のユーザーが既にロックを保持、または並行コミット(同一ファイルでの競合状態)
GET /api/files/{repo_id}/snapshot
現在のリビジョンにおけるリポジトリの完全な状態を返します:すべてのファイル、そのリビジョン、およびそのチャンク。 clone および強制 sync 操作でクライアントが使用します。
クエリパラメータ:
revision(任意):過去のリビジョンでのスナップショット。デフォルト: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
指定したリビジョンでのファイルの内容をダウンロードします(サーバーがチャンクを再構成します)。clone および sync で使用します。
クエリパラメータ:
path:ファイルパス(リポジトリのルートからの相対)revision(任意):リビジョン番号。デフォルト:最新。
Response 200: ファイルのバイナリ内容。
GET /api/files/{repo_id}/history
リポジトリのコミットのページネーションされた履歴。
クエリパラメータ:
limit(1〜100、デフォルト 20)offset(デフォルト 0)path(任意):ファイルパスでフィルタ(このファイルを変更したコミット)author(任意):username でフィルタsince(任意):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
}
}
増分 sync
デスクトップクライアントが効率的に同期するために使用するエンドポイント:
GET /api/files/{repo_id}/sync:あるリビジョン以降に変更されたファイル、delta sync 用。GET /api/files/{repo_id}/deletions:サーバー側で削除されたファイル、削除をローカルに伝播するため。GET /api/files/{repo_id}/list:リポジトリのファイル一覧。POST /api/files/{repo_id}/checkout:ロックを取得し、編集を準備します。
ロック
POST /api/locks/{repo_id}/acquire
パスのリストに対してロックを取得します。取得は独立しています:acquired リストには成功、
failed リストには失敗とその理由が入ります。
ボディ:
{
"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"
}
]
}
}
ロックは heartbeat なしで 60 分後に期限切れになります。expires_at フィールドはこの期限を反映します。
ロックを更新するには /heartbeat を定期的に呼び出し、解放するには /release を呼び出します。
failed の理由:
ALREADY_LOCKED:別のユーザーがロックを保持している(lock_holderとlock_acquired_atフィールドが設定される)PERMISSION_DENIED:このパスに対する write permission がないINVALID_PATH:不正なパス(絶対パス、..を含む、など)
POST /api/locks/{repo_id}/release
あなたが所有するロックを解放します。ボディ:
{
"paths": ["Content/Maps/MainLevel.umap"],
"force": false
}
force: true には force_unlock capability が必要です。このアクションは監査されます。
POST /api/locks/{repo_id}/heartbeat
あなたのロックの last_heartbeat_at を更新します。管理者にアクティビティを見せたい長時間実行の CI ジョブでは、
5 分ごとに実行することを推奨します。
GET /api/locks/{repo_id}/status
リポジトリのすべてのアクティブなロックを一覧表示し、users と files に join して名前を直接返します。
クエリパラメータ: user(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
}
}
コメントとレビュー
GET /api/comments/{repo_id}/comments?commit_hash=<hash>
コミットのコメントをスレッド化して(親 → 子)一覧表示します。
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
コメントを作成します。file_path は任意です(コミットへのコメント vs. ファイルへのコメント)。既存のスレッドに返信するには parent_id を使用します。
ボディ:
{
"commit_hash": "7f3a9b1c...",
"file_path": "Content/Maps/MainLevel.umap",
"body": "LGTM, ship it",
"parent_id": null
}
POST /api/comments/{repo_id}/reviews
コミットにレビューを送信します。approve_changes capability が必要です。
ボディ:
{
"commit_hash": "7f3a9b1c...",
"status": "approved",
"comment": "Lighting looks great, approving"
}
受け付けられる status:approved、changes_requested、pending。
ウォッチリスト
GET /api/watchlist/{repo_id}/watchlist
このリポジトリにおける現在のユーザーの watch パターンを一覧表示します。
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
watch パターンを追加します。ボディ:
{
"pattern": "Content/Characters/Hero/**",
"notify_on": ["commit", "lock"]
}
可能なイベント:commit(コミットがマッチするファイルを変更した)、
lock(ロックが取得された)、review(レビューが投稿された)。
DELETE /api/watchlist/{repo_id}/watchlist/{watch_id}
watch パターンを削除します。{ "success": true } を返します。
プロダクションボード(タスク)
古い modification-requests API は削除されました:プロダクションボードがそれを吸収します
(リクエストはカードになります、origin='request')。エンドポイントは
/api/tasks/{repo_id} の下にネストされています。
| ルート | 説明 |
|---|---|
GET /api/tasks/{repo_id}/board | 完全なボード:カラム + カード |
POST /api/tasks/{repo_id}/tasks | カードを作成 |
PUT/DELETE /api/tasks/{repo_id}/tasks/{task_id} | カードを更新 / 削除 |
PUT /api/tasks/{repo_id}/tasks/{task_id}/assignees | カードにユーザーを割り当て |
GET /api/tasks/{repo_id}/assignable-users | 割り当て可能なユーザーの一覧 |
POST /api/tasks/{repo_id}/columns | ボードのカラムを設定 |
利用可能なサブルート:カードのコメント、assets およびコミットへのリンク、添付ファイル
(カバー画像付き)。カラムの設定には manage_board capability(admin / lead)が必要です。
ビルド
POST /api/builds/{repo_id}/upload?hash=<sha256>
ビルドファイルを <storage_path>/builds/<hash> にアップロードします。
サーバーはクエリパラメータで提供された SHA-256 を受信した内容と照合します。ハッシュがサーバー側に既に存在する場合、
再コピーせずに直ちに 200 を返します(build storage 側の重複排除)。
ヘッダー: Content-Type: application/octet-stream。
ボディ: 生のバイナリ(JSON ラッピングなし)。
制限: ファイルあたり 8 GB。
エラー:
400:hash クエリパラメータが欠落または不正、または計算された SHA-256 が提供されたハッシュと一致しない413:ファイルが 8 GB を超える
POST /api/builds/{repo_id}/publish
すべてのファイルをアップロードした後、ビルドの manifest を登録します。publish_builds capability が必要です。
ボディ:
{
"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}
公開されたビルドを一覧表示します。ダウンロードリンクを見るには download_builds capability が必要です。
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
ビルドからファイルをダウンロードします。download_builds capability が必要です。ビルドの manifest は GET /api/builds/{repo_id}/{build_id}/manifest で取得できます。
管理
すべての /api/admin/* ルートでは、ユーザーが少なくとも 1 つの admin capability
(エンドポイントに応じて:manage_users、manage_permissions、manage_rules など)を持っている必要があります。
以下は簡潔なドキュメントです。ほとんどは標準的な REST パターン(GET/POST/PUT/DELETE)に従います。
| ルート | Capability | 説明 |
|---|---|---|
GET/POST /api/admin/users | manage_users | ユーザーを一覧表示 / 作成 |
PUT/DELETE /api/admin/users/{id} | manage_users | 更新 / 削除 |
POST /api/admin/users/{id}/reset-password | manage_users | パスワードをリセット、token_version をインクリメント |
GET/POST /api/admin/groups | manage_users | グループ管理 |
GET/POST /api/admin/permissions/{repo_id} | manage_permissions | パスごとの glob permission ルール |
GET/POST /api/admin/repositories/{repo_id}/rules | manage_rules | checkin 前の検証ルール |
GET/POST /api/admin/repositories/{repo_id}/webhooks | manage_rules | Discord / Slack / Teams / custom |
GET /api/admin/audit | view_all_activity | フィルタ・ページネーションされた audit log |
GET /api/admin/stats | view_all_activity | Storage metrics、dedup ratio、成長 |
GET /api/admin/repositories/{repo_id}/gc/preview | admin | 実行せずに GC をプレビュー |
POST /api/admin/repositories/{repo_id}/gc | admin | garbage collection を実行 |
POST /api/admin/locks/{id}/force-release | force_unlock | 第三者のロックを強制解除、監査あり |
ヘルスチェック
GET /health
サーバーがデータベースにアクセスできる場合に 200 OK を返す認証不要のエンドポイント。
ロードバランサーやモニタリングのヘルスチェック(Prometheus blackbox、Datadog synthetic など)に使用されます。
Response 200: OK(text/plain)。
Response 503: データベースに到達できない場合。
GET /api/server-info
サーバーのメタデータ(バージョンなど)を返すパブリックで認証不要のエンドポイント。
/health と同様に、auth ミドルウェアで保護されていません。
エラー形式
エラー時、レスポンスは適切な HTTP status を伴う { "success": false, "error": "..." } です。
error フィールドは人間が読めるメッセージです。安定した機械可読コードが必要なクライアントは、
プレフィックスを解析してください(例:"Permission denied: ..." は常に "Permission denied" で始まります)。
| HTTP | 意味 | いつ |
|---|---|---|
| 400 | Bad request | 無効なパラメータ、不正な JSON、ボディが大きすぎる(ハードな 413 制限の前) |
| 401 | Not authenticated | トークンの欠落、期限切れ、無効な署名、または token_version の不一致 |
| 403 | Permission denied | capability の不足、またはパス / リポジトリに対する permission がない |
| 404 | Resource not found | リポジトリ / ファイル / コミット / ユーザーが存在しないかアクセス不可 |
| 409 | Conflict | ロックが既に保持されている、並行コミット、一意制約違反 |
| 413 | Payload too large | ボディが制限を超えた(/files で 1 GB、/builds/upload で 8 GB) |
| 429 | Too many requests | /auth/login のみ(ユーザーごとのレート制限) |
| 500 | Internal server error | DB エラー、IO エラー。サーバー側でログに記録されます。バグを報告する場合は timestamp を添えてください。 |
| 503 | Service unavailable | データベースに到達できない(health check)またはマイグレーション実行中 |