uVersion
日本語
ダウンロード →

Wiki

REST API

uVersion サーバーの HTTP エンドポイントのリファレンス: 認証、リポジトリ、ファイル、ロック、コメント、ウォッチリスト、プロダクションボード、ビルド、管理。

uVersion サーバーは JSON HTTP API を公開します。呼び出しは JWT(JSON Web Token)で認証されます。これはログイン時にサーバーが渡し、 その後の各リクエストで送り返すセッショントークンのことです。このページは、 デスクトップクライアント、エディタープラグイン、そして uversion CLI が使用するエンドポイントを文書化します。 これらを直接呼び出して、uVersion を自前の内部ツール(自作ダッシュボード、監査スクリプト、Webhook など)に 統合できます。

このページは API 全体を網羅するものではありません。ここで説明していないルートのファミリーが いくつか存在します。その一覧は 扱っていない領域 のセクションにあります。

規約

ベース URL

文書化されたすべてのパスは、あなたのインスタンスの URL からの相対です。例では https://uversion.mygamestudio.com を使用します。ご自分のものに置き換えてください。

必須ヘッダー

ヘッダー
AuthorizationBearer <jwt>/api/auth/*/api/server-info(および /health)を除くすべての /api/* ルートで
Content-TypeJSON ボディを伴う POST/PUT には application/json。バイナリアップロード(ビルドファイル)には application/octet-stream
Acceptapplication/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)は このエンベロープを使用しませんsuccessdata もない、生のオブジェクトを返します:

{
  "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 はそのどちらも返しません。ファイルの 生のバイナリ内容を、そのまま返します。

ページネーション

共通のページネーション規約は ありません。各ルートには独自のものがあるか、 まったくないかです。limittotalhas_more も前提にしないでください。

ルート受け付けるパラメータ応答の形
GET /api/files/{repo_id}/history limit(既定 50)、offset(既定 0)、path コミットのフラットな配列。total なし、has_more なし: 配列が limit より少ない要素しか含まないとき、末尾に達したことになります。
GET /api/repositories include_inactive のみ(そしてスーパー管理者に対してのみ尊重されます) フラットな配列。limitoffset も読み取られません。
GET /api/locks/{repo_id}/status なし リポジトリのすべてのロックのフラットな配列。
GET /api/files/{repo_id}/snapshot commit_hash必須 ファイルのフラットな配列。
GET /api/admin/audit pageper_pagelimit/offset ではない、さらにフィルター(user_idactionentity_typefromto スーパー管理者専用。共通規約の不在をよく示しています: ページ番号でページ分けする唯一のルートです。

ここに列挙されていないルートはすべて、その結果全体を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 とまったく同じ形 (tokenusermust_change_password)で、新しいトークンを伴います。

エラー:

  • 401: 無効または期限切れのトークン、無効化されたアカウント、または古い token_version

POST /api/auth/logout

現在のユーザーの すべてのトークン を、すべてのマシンで、 users.token_version を増やすことで無効にします。あらゆる場所で再ログインが必要になります: デスクトップクライアント、 エディタープラグイン、CLI を含みます。

Response 200: { "logged_out": true }

POST /api/auth/validate

トークンがまだ有効かを確認し、対応するユーザーを返します。チェックは is_activetoken_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"
}

validexpires_atrefreshed_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

権限テーブルでフィルターされた、現在のユーザーがアクセスできるリポジトリを列挙します。 リポジトリに対して権限ルールを持たないユーザーはそれを見ません。adminlead のロールはすべてのリポジトリを見ます。

クエリパラメータ: 1つだけ、include_inactive(真偽値、既定 false)で、スーパー管理者に対してのみ尊重されます。 limitoffset もありません: ルートはリスト全体を返します。

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"
    }
  ]
}

返されるフィールドはこれらだけです。特に、ownercurrent_revisionfile_countsize_byteslast_commit_at もありません: リポジトリは API の意味での所有者を持たず、 容量データは管理統計ルートから得られます。

GET /api/repositories/{repo_id}

リポジトリの詳細。リストと同じオブジェクトの形、同じフィールド。

エラー:

  • 403: このリポジトリへのアクセスなし
  • 404: リポジトリが存在しない

POST /api/repositories

リポジトリを作成します。スーパー管理者専用、すなわち admin ロールとそれだけです。これはケイパビリティではありません: チェックは直接ロールに対して行われるため、 project_adminlead403 を受け取ります。製品に 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 に受け付けられる値は addmodifydelete の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_changedbytes_uploadedbytes_dedupedrevision もありません。 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: コミットの数、既定 50
  • offset: 既定 0
  • path(任意): このファイルに触れたコミットのみを残します

読み取られるパラメータはこの3つだけです。authorsince もありません: 未知のパラメータは黙って無視され、それによって もっともらしいがフィルターされていない応答になります。著者や日付でのフィルターは呼び出し元側で行ってください。

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
        }
      ]
    }
  ]
}

totalhas_more も、limitoffset の 再掲もありません。履歴全体をたどるには、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 フィルターも limitoffset もありません。ルートはリポジトリのロック全体を返し、呼び出し元側でフィルターします。

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"
}

受け付けられるステータス: approvedchanges_requestedpending

ウォッチリスト

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 != 与えられた hash
  • 413: ファイル > 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_usersmanage_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意味いつ
400Bad request無効なパラメータ、不正な JSON、ボディが大きすぎる(ハードな 413 制限の前)
401Not authenticatedトークンの欠如、期限切れ、無効な署名、または token_version の不一致
403Permission deniedケイパビリティの欠如、または path / repo への権限なし
404Resource not foundrepo / ファイル / commit / user が存在しないか、アクセス不能
409Conflictロックがすでに保持されている、並行コミット、unique 制約違反
413Payload too largeボディが制限を超える(/files で 1 GB、/builds/upload で 8 GB)
429Too many requests/auth/login のみ(ユーザーごとのレート制限)
500Internal server errorDB エラー、IO エラー。サーバー側でログに記録、バグを報告する場合は timestamp を添えてください。
503Service unavailableデータベースに到達不能(health check)、または移行が進行中