Wiki
REST API
uVersion サーバーの HTTP エンドポイントのリファレンス: 認証、リポジトリ、ファイル、ロック、コメント、ウォッチリスト、プロダクションボード、ビルド、管理。
uVersion サーバーは JSON HTTP API を公開します。呼び出しは
JWT(JSON Web Token)で認証されます。これはログイン時にサーバーが渡し、
その後の各リクエストで送り返すセッショントークンのことです。このページは、
デスクトップクライアント、エディタープラグイン、そして uversion CLI が使用するエンドポイントを文書化します。
これらを直接呼び出して、uVersion を自前の内部ツール(自作ダッシュボード、監査スクリプト、Webhook など)に
統合できます。
このページは API 全体を網羅するものではありません。ここで説明していないルートのファミリーが いくつか存在します。その一覧は 扱っていない領域 のセクションにあります。
規約
ベース URL
文書化されたすべてのパスは、あなたのインスタンスの URL からの相対です。例では
https://uversion.mygamestudio.com を使用します。ご自分のものに置き換えてください。
必須ヘッダー
| ヘッダー | 値 |
|---|---|
Authorization | Bearer <jwt>。/api/auth/* と /api/server-info(および /health)を除くすべての /api/* ルートで |
Content-Type | JSON ボディを伴う POST/PUT には application/json。バイナリアップロード(ビルドファイル)には application/octet-stream。 |
Accept | application/json を推奨(サーバーは既定で JSON を返します) |
応答形式は2つあり、どちらを読んでいるかを知る必要があります
サーバーの応答形式は 1つ ではなく2つあり、それらを混同することは、統合を始める人にとって 最も高くつく誤りです。
1. 認証済みルート(/api/auth/* を除くすべての /api/*)は
エンベロープで応答します:
{
"success": true,
"data": { /* payload */ }
}
エラーの場合、これらの同じルートは次のように応答します:
{
"success": false,
"error": "No write permission on this repository"
}
success フィールドは常に存在します。data は成功時に、
error は失敗時に存在し、両方が同時に存在することはありません。
2. 認証ルート(/api/auth/login、
/refresh、/validate、/logout、/register、
/change-password)は このエンベロープを使用しません。
success も data もない、生のオブジェクトを返します:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": { "id": 12, "username": "alice", ... },
"must_change_password": false
}
そしてそれらのエラーは、success を含まない単一フィールドのオブジェクトです:
{ "error": "Invalid credentials" }
実際的な帰結: /api/auth/login では、トークンは .token で読み取り、
.data.token では読み取りません。.data.token を問い合わせるスクリプトは、
目に見えるエラーもなく null を受け取ります。
最後に、GET /api/files/{repo_id}/content はそのどちらも返しません。ファイルの
生のバイナリ内容を、そのまま返します。
ページネーション
共通のページネーション規約は ありません。各ルートには独自のものがあるか、
まったくないかです。limit も total も has_more も前提にしないでください。
| ルート | 受け付けるパラメータ | 応答の形 |
|---|---|---|
GET /api/files/{repo_id}/history |
limit(既定 50)、offset(既定 0)、path |
コミットのフラットな配列。total なし、has_more なし: 配列が limit より少ない要素しか含まないとき、末尾に達したことになります。 |
GET /api/repositories |
include_inactive のみ(そしてスーパー管理者に対してのみ尊重されます) |
フラットな配列。limit も offset も読み取られません。 |
GET /api/locks/{repo_id}/status |
なし | リポジトリのすべてのロックのフラットな配列。 |
GET /api/files/{repo_id}/snapshot |
commit_hash、必須 |
ファイルのフラットな配列。 |
GET /api/admin/audit |
page と per_page、limit/offset ではない、さらにフィルター(user_id、action、entity_type、from、to) |
スーパー管理者専用。共通規約の不在をよく示しています: ページ番号でページ分けする唯一のルートです。 |
ここに列挙されていないルートはすべて、その結果全体を1回の呼び出しで返すものと 考えてください。
レート制限
一般的なレート制限はありません: Unreal プロジェクトには数千のファイルがあり、 一括操作(ロックの取得、解放)は絶えずスロットルを引き起こしてしまうためです。 したがって API を大量に呼び出せます。
制限されるルートは /api/auth/login の1つだけです:
ユーザー名ごとに15分間で5回の失敗した試行。成功したログインは
カウントされません。
トークンの失効
各セッショントークンは tv フィールド(token version)を持ち、これはデータベースの
users.token_version 列を反映します。サーバーは各リクエストで両者を比較します。
POST /api/auth/logout の呼び出し、管理者によるパスワードのリセット、
またはアカウントの無効化はこの値を増やし、それによってそのユーザーの
既存のすべてのトークンが即座に、すべてのマシン上で無効になります。
Curl
ターミナルから API を呼び出すため。.token に注目してください:
/api/auth/login の応答は生のオブジェクトであり、たどるべき .data はありません。
TOKEN=$(curl -s -X POST https://uversion.mygamestudio.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"secret"}' | jq -r '.token')
# 一方、認証済みルートはこのエンベロープを確かに使用します:
curl -s -H "Authorization: Bearer $TOKEN" \
https://uversion.mygamestudio.com/api/repositories | jq '.data'
認証
注意: このセクションのどのルートも
{"success", "data"} エンベロープを使用しません。生のオブジェクトを返し、それらのエラーは
{"error": "..."} の形です。
POST /api/auth/login
ユーザーを認証し、30日間有効なセッショントークンを返します。
Body:
{
"username": "alice",
"password": "secret"
}
Response 200:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 12,
"username": "alice",
"email": "alice@mygamestudio.com",
"role": "lead"
},
"must_change_password": false
}
いかなる expires_at フィールドも存在しません: 有効期間はトークン自体から
読み取るか、サーバー設定から導かれます(既定で30日)。
must_change_password を無視しないでください。 このフィールドは、
アカウントがまだ一時パスワードで動作しているときに true になります: インストーラーが
初期管理者アカウントのために生成したもの、または管理者がリセット時にちょうど設定したものです。
これを見ないクライアントは、ユーザーをその暫定パスワードのまま無期限に放置します。期待される
動作は、他のいかなるアクションよりも前に、ただちに POST /api/auth/change-password へ
リダイレクトすることです。
エラー:
401: 無効な認証情報、または無効化されたアカウント429: このユーザー名について15分のウィンドウ内で5回の失敗した試行に達した
サーバーは、アカウントが存在しないときはダミーのハッシュに対してであっても、常にパスワードの Argon2id 検証を実行します。したがって応答は両方の場合で同じ時間を要し、これにより リクエストの時間を計ってアカウントの存在を推測することを防ぎます。
POST /api/auth/refresh
パスワードを再度通さずにセッショントークンを更新します。
ヘッダー: Authorization: Bearer <現在のトークン>。ボディなし。
Response 200: /login とまったく同じ形
(token、user、must_change_password)で、新しいトークンを伴います。
エラー:
401: 無効または期限切れのトークン、無効化されたアカウント、または古いtoken_version
POST /api/auth/logout
現在のユーザーの すべてのトークン を、すべてのマシンで、
users.token_version を増やすことで無効にします。あらゆる場所で再ログインが必要になります: デスクトップクライアント、
エディタープラグイン、CLI を含みます。
Response 200: { "logged_out": true }。
POST /api/auth/validate
トークンがまだ有効かを確認し、対応するユーザーを返します。チェックは
is_active と token_version にも及ぶため、失効したトークンは
まだ期限が切れていなくても拒否されます。
ボディに注意: これはオブジェクトではなく、生の JSON 文字列、 すなわち二重引用符で囲まれたトークンです。
curl -X POST https://uversion.mygamestudio.com/api/auth/validate \
-H "Content-Type: application/json" \
-d '"eyJhbGciOiJIUzI1NiIs..."'
Response 200: 生のユーザーオブジェクト。
{
"id": 12,
"username": "alice",
"email": "alice@mygamestudio.com",
"role": "lead"
}
valid も expires_at も refreshed_token もありません:
有効性は HTTP コード(200 または 401)から読み取り、更新は
/api/auth/refresh を通じて行い、このルートを通じては決して行いません。
POST /api/auth/register
このルートは既定で拒否します。 オープン登録は、運用者がサーバー設定で
明示的に有効化していない限り無効です。そうでない場合、応答は
403 で {"error": "Open registration is disabled; contact your administrator"} になります。
通常の運用では、アカウントは管理を通じて作成されます:
POST /api/admin/users、または管理パネルのユーザータブ。
/register に依存する統合を構築しないでください。
POST /api/auth/change-password
現在のユーザーのパスワードを変更します。token_version を増やし、それによって
以前のすべてのトークンを、ちょうどその呼び出しに使われたものを含めて無効にします。
リポジトリ
GET /api/repositories
権限テーブルでフィルターされた、現在のユーザーがアクセスできるリポジトリを列挙します。
リポジトリに対して権限ルールを持たないユーザーはそれを見ません。admin と
lead のロールはすべてのリポジトリを見ます。
クエリパラメータ: 1つだけ、include_inactive(真偽値、既定
false)で、スーパー管理者に対してのみ尊重されます。
limit も offset もありません: ルートはリスト全体を返します。
Response 200:
{
"success": true,
"data": [
{
"id": 1,
"name": "hero-rpg",
"description": "Main RPG project",
"storage_path": "/var/lib/uversion/data/hero-rpg",
"is_active": true,
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-05-15T08:30:00Z"
},
{
"id": 2,
"name": "shared-assets",
"description": "Shared asset library",
"storage_path": "/var/lib/uversion/data/shared-assets",
"is_active": true,
"created_at": "2026-02-01T09:00:00Z",
"updated_at": "2026-05-02T11:12:00Z"
}
]
}
返されるフィールドはこれらだけです。特に、owner も
current_revision も file_count も size_bytes も
last_commit_at もありません: リポジトリは API の意味での所有者を持たず、
容量データは管理統計ルートから得られます。
GET /api/repositories/{repo_id}
リポジトリの詳細。リストと同じオブジェクトの形、同じフィールド。
エラー:
403: このリポジトリへのアクセスなし404: リポジトリが存在しない
POST /api/repositories
リポジトリを作成します。スーパー管理者専用、すなわち admin
ロールとそれだけです。これはケイパビリティではありません: チェックは直接ロールに対して行われるため、
project_admin や lead は 403 を受け取ります。製品に
create_repos ケイパビリティは存在しません。
Body:
{
"name": "new-project",
"description": "任意の説明"
}
検証:
name: 1〜255文字。サーバーは文字セットの制約を課しません。ディスク上のストレージパスは、名前を正規化して導かれます。description: 任意。
一意性は、サーバー規模で 名前のみ にかかります。所有者の概念はないため、 「所有者ごと」の一意性もありません。
エラー:
400: 空の名前、または255文字を超える403: 呼び出し元がadminロールを持たない409: すでにこの名前のリポジトリがある
ファイル
POST /api/files/{repo_id}/upload-chunks
断片のリストではなく、ファイル1つ全体 を送信します。ルート名は 誤解を招きます: ファイルをブロック(chunks)に分割するのは サーバー であり、 あなたではありません。呼び出し元がこの分割を自分で行う必要は決してありません。
SHA-256 のハッシュで認識される同一のブロックは 重複排除されます: すでにサーバー上に存在するブロックは、どのファイルやどのリポジトリから来たものであっても、 2度目に保存されることはありません。これが、端を少し変更した大きなバイナリファイルが ディスク容量をほとんど消費しない理由です。
Body: base64 でエンコードされた完全なファイルを含む、単一フィールドのオブジェクト。
{
"data": "<base64 でエンコードされたファイル全体>"
}
それ以外の形、特に chunks 配列は、サーバーによって拒否されます。
Response 200:
{
"success": true,
"data": {
"chunks": [
{ "hash": "abc123...", "offset": 0, "size": 1048576, "compressed_size": 423152 },
{ "hash": "def456...", "offset": 1048576, "size": 2097152, "compressed_size": 891204 }
],
"chunks_stored": 1,
"chunks_deduplicated": 1
}
}
chunks 配列は、完全なオブジェクトを含めて そのまま、続く
POST /commit で再利用します。chunks_stored は実際にディスクに
書き込まれたブロックを、chunks_deduplicated はすでに存在していたものを数えます。
権限: リポジトリへの書き込みが必要です。そうでなければ 403。
制限: リクエストボディは 1 GB に制限されます(security.max_body_size_files
で調整可能)。この制限より大きいファイルは、このルートを通ることができません。
POST /api/files/{repo_id}/commit
ファイルとそのブロックのリストから、アトミックなコミットを作成します。すべてのファイルが 通るか、まったく通らないかのどちらかです。
action に受け付けられる値は add、modify、delete の3つだけです
added ではなく、modified でもなく、deleted でもありません。
これは形式上の細部ではありません。サーバーは文字どおり
action == "delete" をテストし、それ以外すべて を追加または
変更として扱います。したがって "deleted" を送っても何も削除されません: ファイルは
空の chunks 配列とともに書き込みブランチに行き、サーバーは
空の内容のリビジョンを記録します。エラーは一切送出されません。ファイルは存在したままで、その最新
バージョンは空で上書きされ、その損失は他の誰かの次の sync でしか
見えません。
Body:
{
"message": "Updated main level + hero pose pass",
"commit_hash": "任意: 複数のバッチを単一のコミットにまとめるため",
"files": [
{
"path": "Content/Maps/MainLevel.umap",
"action": "modify",
"chunks": [
{ "hash": "abc123...", "offset": 0, "size": 1048576, "compressed_size": 423152 },
{ "hash": "def456...", "offset": 1048576, "size": 2097152, "compressed_size": 891204 }
]
},
{
"path": "Content/Characters/NewVillain.uasset",
"action": "add",
"chunks": [
{ "hash": "fed789...", "offset": 0, "size": 524288, "compressed_size": 201004 }
]
},
{
"path": "Content/OldAsset.uasset",
"action": "delete",
"chunks": []
}
]
}
chunks は任意ではありません、delete の場合でも。
フィールドは存在しなければなりません: それを省くとリクエスト全体の逆シリアル化が失敗します。
削除には、空の配列を送ってください。
配列には文字列ではなく オブジェクト が入ります: /upload-chunks が
返した {hash, offset, size, compressed_size} エントリを変更せずに再利用してください。
各 hash は64文字の16進ダイジェストでなければならず、さもなければコミット全体が
400 で拒否されます。
ルートレベルの commit_hash フィールドは任意です。複数の連続した呼び出しを
ただ1つの同じコミットに担わせるために使い、これはデスクトップクライアントが大きな
アップロードをバッチに分割するときに行うものです。省略すると、サーバーが1つを計算します。
Response 200:
{
"success": true,
"data": {
"commit_hash": "7f3a9b1c2d3e4f...",
"files_committed": 3,
"revisions": [
{ "path": "Content/Maps/MainLevel.umap", "revision_number": 12 },
{ "path": "Content/Characters/NewVillain.uasset", "revision_number": 1 },
{ "path": "Content/OldAsset.uasset", "revision_number": 8 }
]
}
}
返されるフィールドはこれらだけです。files_changed も
bytes_uploaded も bytes_deduped も revision もありません。
revision_number はリポジトリのバージョン番号ではなく ファイルごと の
カウンターであることに注意してください: コミットを識別するのは commit_hash であり、
状態を参照するために保持すべきはそれです。
エラー:
400: 不正なボディ、無効なブロックダイジェスト(期待: 64個の16進文字)、chunksフィールドの欠如403: いずれかのパスに書き込み権限がない409: 変更されたファイルに他の誰かが保持するロック、または同じファイルへの並行コミット
GET /api/files/{repo_id}/snapshot
ある特定のコミット時点でのリポジトリの状態を返します: 存在するファイルの一覧を、 そのリビジョン番号とサイズとともに。デスクトップクライアントがクローンと 強制同期のために使用します。
クエリパラメータ:
-
commit_hash: 必須。基準点となるコミットのハッシュです。 このパラメータがなければリクエストは拒否され、「最新の状態」という既定値は 存在しません。
いかなる revision パラメータも存在しません。スナップショットはコミットハッシュで
要求し、番号では決して要求しません。未知のコミットは 404 を返します。
Response 200: 包む object のない、フラットな配列。
{
"success": true,
"data": [
{
"path": "Content/Maps/MainLevel.umap",
"revision_number": 12,
"file_size": 84934656
},
{
"path": "Content/Characters/Hero.uasset",
"revision_number": 3,
"file_size": 5242880
}
]
}
応答にはブロックの一覧は 含まれません。内容を取得するには、ファイルを
サーバー側で再構成する GET /api/files/{repo_id}/content を通ってください。
GET /api/files/{repo_id}/content
あるリビジョンでのファイルの内容をダウンロードします(サーバーがチャンクを再構成します)。クローンと sync が使用します。
クエリパラメータ:
path: ファイルのパス(repo のルートからの相対)revision(任意): リビジョン番号。既定: 最新。
Response 200: ファイルのバイナリ内容。
GET /api/files/{repo_id}/history
リポジトリのコミット履歴、最新から最古へ。
クエリパラメータ:
limit: コミットの数、既定 50offset: 既定 0path(任意): このファイルに触れたコミットのみを残します
読み取られるパラメータはこの3つだけです。author も
since もありません: 未知のパラメータは黙って無視され、それによって
もっともらしいがフィルターされていない応答になります。著者や日付でのフィルターは呼び出し元側で行ってください。
Response 200: コミットのフラットな配列。
{
"success": true,
"data": [
{
"commit_hash": "7f3a9b1c...",
"message": "Fixed lighting in main level",
"author": "alice",
"created_at": "2026-05-15T08:30:00Z",
"files": [
{
"path": "Content/Maps/MainLevel.umap",
"revision_number": 12,
"file_size": 84934656
}
]
}
]
}
total も has_more も、limit と offset の
再掲もありません。履歴全体をたどるには、limit より少ない要素を受け取るまで
offset を増やしてください。
各コミットは、触れたファイルの一覧を直接持ちます。リビジョンが削除であるエントリは そのように印が付き、ダウンロードする内容はありません。
増分 sync
デスクトップクライアントが効率的に同期するために使用するエンドポイント:
GET /api/files/{repo_id}/sync: あるリビジョン以降に変更されたファイル、デルタ sync のため。GET /api/files/{repo_id}/deletions: サーバー側で削除されたファイル、削除をローカルに伝播するため。GET /api/files/{repo_id}/list: repo のファイルの一覧。POST /api/files/{repo_id}/checkout: ロックを取得し、編集を準備します。
ロック
ロックは、明示的に解放されるまで保持されます: checkin により、 revert により、または管理者による強制ロック解除により。自動的な 期限切れは一切ありません、1時間後にも、1か月後にも。
expires_at フィールドは、データベースの対応する列が空の値を
受け付けないという理由だけで存在します。サーバーはそこに100年のセンチネル値を書き込みます: 今日
取ったロックは、2126年あたりの期限を表示します。このフィールドの上には何も構築せず、
この日付をユーザーに表示しないでください。
したがって heartbeat は何も延長しません。監視シグナルであり、その唯一の目的は どのロックがまだ活発に使われているかを管理者に示すことです。
POST /api/locks/{repo_id}/acquire
パスのリストに対してロックを取得します。取得は独立しています: acquired
リストには成功が、failed リストには失敗がその理由とともに入ります。
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",
"expires_at": "2126-05-15T14:30:00Z"
}
],
"failed": [
{
"path": "Content/Characters/Hero.uasset",
"reason": "File is locked",
"locked_by": "bob"
}
]
}
}
この例の2126年の期限は誤植ではありません: 上で説明したセンチネル値です。ロックは 永続的です。
失敗理由。 reason フィールドは、表示されることを意図した
英語の文 であり、安定したコードではありません。この文字列を比較するロジックを
書かず、その接頭辞を「パース」しないでください: バージョンごとに言い換えられることがあります。
現在生成される値は次のとおりです:
reason | 意味 | locked_by |
|---|---|---|
File is locked | 他の誰かがすでにロックを保持している | その人の名前 |
No write permission | このパスに書き込み権がない | null |
Failed to create lock | ロックの設定がデータベースで失敗した | null |
Database error | このパスでのデータベースエラー | null |
無効なパス(絶対、.. を含む、空、4096文字を超える、またはヌルバイトを
帯びる)は failed にエントリを生成しません: リクエスト全体を失敗させます。
POST /api/locks/{repo_id}/release
あなたが保持するロックを解放します。Body:
{
"paths": ["Content/Maps/MainLevel.umap"]
}
このルートに force フィールドはありません。 1つ追加しても効果は
ありません: サーバーは知らないフィールドを無視し、リクエストは成功し、他人のロックは
そのまま残ります。それを当てにする統合者は、ファイルを解放したと思い込みますが、そうではありません。
他の誰かのロックを外すための唯一のルートは
POST /api/admin/locks/{lock_id}/force-release です。当該リポジトリの管理に
留保され、その操作は監査ログに記録されます。ロックの識別子を受け取り、
それは GET /api/locks/{repo_id}/status で得られます。
POST /api/locks/{repo_id}/heartbeat
あなたのロックのアクティビティのタイムスタンプを更新します。これは何も延長しません、 何も期限切れにならないからです: 監視シグナルであり、管理者が まだ使われているロックと忘れられたロックを区別できるようにします。
Body: 包む object のない、パスの JSON 配列を直接。
["Content/Maps/MainLevel.umap", "Content/Characters/Hero.uasset"]
無効なパスは、古い追跡の残りが他を妨げないように、バッチ全体を失敗させる代わりに 個別に無視されます。
GET /api/locks/{repo_id}/status
リポジトリのすべてのロックを、ファイル名とそれを保持する人の名前とともに列挙します。
クエリパラメータ: なし。 user フィルターも limit も
offset もありません。ルートはリポジトリのロック全体を返し、呼び出し元側でフィルターします。
Response 200: 包む object も total もない、フラットな配列。
{
"success": true,
"data": [
{
"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",
"expires_at": "2126-05-15T14:30:00Z"
}
]
}
id フィールドは、POST /api/admin/locks/{lock_id}/force-release に
渡すものです。
コメント & レビュー
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。
Body:
{
"commit_hash": "7f3a9b1c...",
"file_path": "Content/Maps/MainLevel.umap",
"body": "LGTM, ship it",
"parent_id": null
}
POST /api/comments/{repo_id}/reviews
コミットにレビューを送信します。approve_changes ケイパビリティが必要です。
Body:
{
"commit_hash": "7f3a9b1c...",
"status": "approved",
"comment": "Lighting looks great, approving"
}
受け付けられるステータス: approved、changes_requested、pending。
ウォッチリスト
GET /api/watchlist/{repo_id}/watchlist
このリポジトリにおける現在のユーザーのウォッチパターンを列挙します。
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
ウォッチパターンを追加します。Body:
{
"pattern": "Content/Characters/Hero/**",
"notify_on": ["commit", "lock"]
}
可能なイベント: commit(コミットが一致するファイルを変更した)、
lock(ロックが取得された)、review(レビューが投稿された)。
DELETE /api/watchlist/{repo_id}/watchlist/{watch_id}
ウォッチパターンを削除します。{ "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 | ボードの列を設定します |
同じく利用可能なサブルート: カードのコメント、アセットとコミットへのリンク、そして添付
(カバー画像付き)。列の設定には manage_board ケイパビリティ(admin / lead)が
必要です。
ビルド
POST /api/builds/{repo_id}/upload?hash=<sha256>
ビルドファイルを <storage_path>/builds/<hash> にアップロードします。
サーバーはクエリパラメータで与えられた SHA-256 を、受け取った内容に対して検証します。ハッシュがすでに
サーバー側に存在する場合、再コピーせずに即座に 200 を返します(ビルドストレージ側での重複排除)。
ヘッダー: Content-Type: application/octet-stream。
Body: 生のバイナリ(JSON ラッピングなし)。
制限: ファイルあたり 8 GB。
エラー:
400: hash クエリパラメータが欠落または不正、または計算された SHA-256 != 与えられた hash413: ファイル > 8 GB
POST /api/builds/{repo_id}/publish
すべてのファイルのアップロード後に、ビルドのマニフェストを登録します。publish_builds ケイパビリティが必要です。
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}
このプロジェクトの公開済みビルドを列挙します。
ビルドへのアクセスはプロジェクトごとに与えられます。 download_builds
ケイパビリティだけでは十分ではありません: サーバー全体に及ぶため、アカウントが単なる
傍観者ではないことを述べるだけです。それに加えて、リポジトリへのアクセスか、このプロジェクトの
ビルドに対する明示的な許可のいずれかが必要で、これはその管理者が
/api/admin/repositories/{repo_id}/build-access を通じて付与します。プロジェクト管理者は
自分が管理するものに当然アクセスでき、スーパー管理者はすべてにアクセスできます。
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 ケイパビリティが必要です。ビルドのマニフェストは GET /api/builds/{repo_id}/{build_id}/manifest を通じて入手できます。
管理
これらのルートを実際に守っているもの
予想されるかもしれないことに反して、/api/admin/* ルートは
それぞれがケイパビリティで守られているわけではありません。ほとんどすべてが ロール に
かかる 4つのチェック のいずれかを通ります。capability はロールに結び付いた名前付きの権利です。
重要な点は、それが
サーバー全体で有効 であることです。リポジトリへの参照を一切持たないからです。したがって
誰かをプロジェクトに区切るために使うことは決してできません。
| チェック | 通る対象 | 何のためか |
|---|---|---|
| スーパー管理者 | admin ロール、そしてそれだけ |
サーバー全体に関わるすべて: アカウント、グループ、監査ログ、グローバル統計、ライセンス、サーバー更新。 |
| この リポジトリの管理者 | admin、またはこの特定のリポジトリを管理する project_admin |
プロジェクトに固有のすべて: 権限、検証ルール、Webhook、ビルドへのアクセス、強制ロック解除、ガベージコレクション。 |
| サーバー全体のケイパビリティ | 名前付きケイパビリティを持つあらゆるロール | いくつかの横断的なルート。注意: ケイパビリティは すべての リポジトリで有効であり、それがその性質です。1つも持たない project_admin は、代わりに自分が管理するリポジトリでのみ許可されます。 |
| 入場のみ | admin または project_admin |
中に入れますが、何も認可しません。それを用いるルートは、その後、自らの結果を呼び出し元が管理するリポジトリに自分で制限しなければなりません。 |
言い換えれば: manage_users と manage_permissions
ケイパビリティは紙の上でしか存在しません。インストール時にデータベースには確かに
作成されますが、どのコード行もそれらを問い合わせません。ロールに付与しても何も変わりません。
manage_users を与えられたからといって admin でないアカウントが
ユーザーを管理できると仮定する統合を書かないでください: 403 を受け取ります。
| ルート | 実際のチェック | 説明 |
|---|---|---|
GET /api/admin/users | 入場のみ、その後フィルター | project_admin は自分の範囲のアカウントしか見ません |
POST /api/admin/users | 入場のみ | アカウントを作成します |
PUT/DELETE /api/admin/users/{id} | スーパー管理者 | アカウントを更新または削除します |
POST /api/admin/users/{id}/reset-password | スーパー管理者 | パスワードをリセットし、アカウントのすべてのトークンを失効させます |
GET/POST /api/admin/groups | スーパー管理者 | グループはグローバルで、プロジェクトごとに委譲できません |
GET/POST /api/admin/permissions/{repo_id} | このリポジトリの管理者 | パスパターンごとの権限ルール、グループまたはユーザーに付与 |
GET/POST /api/admin/repositories/{repo_id}/rules | このリポジトリの管理者 | 送信前に適用される検証ルール |
GET/POST /api/admin/repositories/{repo_id}/webhooks | このリポジトリの管理者 | Discord、Slack、Teams、または汎用 Webhook |
GET /api/admin/locks | 入場のみ、その後フィルター | ロック、管理するリポジトリに限定 |
POST /api/admin/locks/{lock_id}/force-release | このリポジトリの管理者 | 他の誰かのロックを外します。監査ログに記録 |
GET /api/admin/audit | スーパー管理者 | 監査ログ、フィルター可能でページ分け |
GET /api/admin/stats | スーパー管理者 | ストレージ容量、重複排除率、増加 |
GET /api/admin/licence | スーパー管理者 | ライセンスの状態と消費されたシート |
GET /api/admin/repositories/{repo_id}/gc/preview | このリポジトリの管理者 | 何も削除せずにガベージコレクションをシミュレートします |
POST /api/admin/repositories/{repo_id}/gc | このリポジトリの管理者 | ガベージコレクションを起動します |
ロール、その序列、そして各自ができることの完全な詳細は、ロールと権限に 充てられたページにあります。
このページで扱っていない API 領域
以下のルートのファミリーは存在し、サーバーによってマウントされ提供されていますが、ここでは 説明していません。統合でそれらが必要な場合、今日もっとも信頼できるのは、デスクトップ クライアントが行う呼び出しを観察するか、私たちに連絡することです。
| 接頭辞 | 何を扱うか |
|---|---|
/api/advisor/* | Project Health: Unreal プロジェクトの監査、その所見、柱ごとのスコア、そして無視する項目の仕分け。 |
/api/distribution/* | プロジェクト間の配布: ソースリポジトリとターゲットリポジトリの間のリンク、あるプロジェクトから別のプロジェクトへのファイルの公開、履歴。 |
/api/binaries/* | コミットに結び付いた、UnrealGameSync のモデルによる事前コンパイル済みエディターバイナリ。 |
/api/watchlist/* | 上で説明したウォッチパターンを超えて: 通知と受信箱。 |
/api/profile/* | 現在のユーザーのプロファイル。 |
/api/admin/repositories/{repo_id}/admins | 誰がリポジトリを管理するか: プロジェクト管理者の指名と解任。 |
/api/admin/repositories/{repo_id}/build-access | 誰がこのプロジェクトのビルドをダウンロードできるか、プロジェクトごとに付与。 |
/api/admin/licence | ライセンスの状態と消費されたシート。 |
/api/admin/server/* | 管理パネルからのサーバー更新。 |
ヘルスチェック
GET /health
認証不要 のエンドポイントで、サーバーがデータベースにアクセスできれば 200 OK を返します。
ロードバランサーや監視のヘルスチェックに使用します(Prometheus blackbox、Datadog synthetic など)。
Response 200: OK(text/plain)。
Response 503: データベースに到達できない場合。
GET /api/server-info
サーバーのメタデータ(バージョンなど)を返す 公開・認証不要 のエンドポイント。
/health と同様に、認証ミドルウェアで保護されていません。
エラー形式
エラーの場合、認証済みルートは { "success": false, "error": "..." } を、
/api/auth/* ルートは { "error": "..." } を返します。どちらの場合も
適切な HTTP コードを伴います。
error フィールドは、コードではなく 人間に向けたメッセージ です。
安定したエラーコードのカタログは、ボディにもヘッダーにも存在しません。したがってその内容の上に
ロジックを構築せず、その接頭辞を解析しないでください: これらの文はバージョンごとに言い換えられ、
黙って壊れる文字列テストは、テストが全くないよりも悪いのです。
振る舞いを分岐させる根拠にできるのは HTTP コードだけです。 下の表が その読み方を示します。
| HTTP | 意味 | いつ |
|---|---|---|
| 400 | Bad request | 無効なパラメータ、不正な JSON、ボディが大きすぎる(ハードな 413 制限の前) |
| 401 | Not authenticated | トークンの欠如、期限切れ、無効な署名、または token_version の不一致 |
| 403 | Permission denied | ケイパビリティの欠如、または path / repo への権限なし |
| 404 | Resource not found | repo / ファイル / commit / user が存在しないか、アクセス不能 |
| 409 | Conflict | ロックがすでに保持されている、並行コミット、unique 制約違反 |
| 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)、または移行が進行中 |