uVersion
日本語
ダウンロード →

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-TypeJSON ボディを伴う POST/PUT には application/json。バイナリアップロード(build files)には application/octet-stream
Acceptapplication/json を推奨(サーバーはデフォルトで JSON を返します)

統一されたレスポンス形式

すべてのルートは次のエンベロープで応答します:

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

エラー時:

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

success フィールドは常に存在し、リクエストが成功したかどうかを示します。 成功時には data フィールドが存在します。失敗時には error フィールドが存在します。 両方が同時に存在することはありません。

ページネーション

長くなる可能性のあるリストを返すルートは、クエリパラメータとして limitoffset を受け付けます(?limit=20&offset=40)。レスポンスには反復処理を容易にするための totalhas_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 パターンを持たないユーザーには表示されません。

クエリパラメータ: limitoffset

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:無効または長すぎる名前
  • 403create_repos capability が不足
  • 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"
    }
  ]
}

可能なアクション:addedmodifieddeletedaddedmodified の場合、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:メッセージが空、アクションのないファイル、存在しないチャンクを参照
  • 403checkin capability がない、またはいずれかのファイルに対する 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_holderlock_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 でフィルタ)、limitoffset

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:approvedchanges_requestedpending

ウォッチリスト

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_usersmanage_permissionsmanage_rules など)を持っている必要があります。 以下は簡潔なドキュメントです。ほとんどは標準的な REST パターン(GET/POST/PUT/DELETE)に従います。

ルートCapability説明
GET/POST /api/admin/usersmanage_usersユーザーを一覧表示 / 作成
PUT/DELETE /api/admin/users/{id}manage_users更新 / 削除
POST /api/admin/users/{id}/reset-passwordmanage_usersパスワードをリセット、token_version をインクリメント
GET/POST /api/admin/groupsmanage_usersグループ管理
GET/POST /api/admin/permissions/{repo_id}manage_permissionsパスごとの glob permission ルール
GET/POST /api/admin/repositories/{repo_id}/rulesmanage_rulescheckin 前の検証ルール
GET/POST /api/admin/repositories/{repo_id}/webhooksmanage_rulesDiscord / Slack / Teams / custom
GET /api/admin/auditview_all_activityフィルタ・ページネーションされた audit log
GET /api/admin/statsview_all_activityStorage metrics、dedup ratio、成長
GET /api/admin/repositories/{repo_id}/gc/previewadmin実行せずに GC をプレビュー
POST /api/admin/repositories/{repo_id}/gcadmingarbage collection を実行
POST /api/admin/locks/{id}/force-releaseforce_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意味いつ
400Bad request無効なパラメータ、不正な JSON、ボディが大きすぎる(ハードな 413 制限の前)
401Not authenticatedトークンの欠落、期限切れ、無効な署名、または token_version の不一致
403Permission deniedcapability の不足、またはパス / リポジトリに対する permission がない
404Resource not foundリポジトリ / ファイル / コミット / ユーザーが存在しないかアクセス不可
409Conflictロックが既に保持されている、並行コミット、一意制約違反
413Payload too largeボディが制限を超えた(/files で 1 GB、/builds/upload で 8 GB)
429Too many requests/auth/login のみ(ユーザーごとのレート制限)
500Internal server errorDB エラー、IO エラー。サーバー側でログに記録されます。バグを報告する場合は timestamp を添えてください。
503Service unavailableデータベースに到達できない(health check)またはマイグレーション実行中