本文へ移動
petrichor

Web API

Petrichor のデータを、プログラムから調べたり登録したりするための API です。リクエストとレスポンスは JSON で、GUID は小文字の 32 桁の 16 進数です。

ベース URL は https://api.natsuneko.com/petrichor/v2 です。

エンドポイント

GET は認証なしで使え、結果は Cache-Control: public, s-maxage=300 でキャッシュされます。

メソッドパス内容ログインキャッシュ
GET/assets/{guid}GUID を含むパスと Release-300 秒
POST/assets/lookupGUID の一括照合-なし
GET/packages?q=パッケージの検索-300 秒
GET/packages/{id}パッケージの詳細-300 秒
GET/releases/{id}Release の詳細-300 秒
GET/releases/{id}/assets?cursor=&path=Release のアセット一覧-300 秒
POST/submissionsマニフェストの登録必要なし
GET/me/submissions自分の登録の一覧必要なし

Release の形

多くのレスポンスに含まれる Release は、次の形です。status は verified (検証済み)、legacy、unverified のいずれかです。

{
  "id": "clx0release",
  "version": "3.7.0",
  "status": "verified",
  "submitterCount": 2,
  "assetCount": 1284,
  "manifestId": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "createdAt": "2026-11-01T00:00:00.000Z",
  "package": { "id": "clx0package", "name": "VRChat SDK - Base" }
}

Release の配列は、検証済みと Legacy が先、未検証が後で、それぞれの中では新しい順に並びます。

GUID を調べる

GET /assets/{guid}

その GUID のパスと種類、それを含む Release を返します。大文字の GUID も受け付けます。登録がない、または GUID の形式が正しくないときは 404 です。

{
  "guid": "0123456789abcdef0123456789abcdef",
  "paths": [
    {
      "path": "Packages/com.vrchat.base/Runtime/Foo.cs",
      "type": "MonoScript",
      "releases": [ /* Release の配列 */ ]
    }
  ]
}

POST /assets/lookup

GUID をまとめて照合します。GUID は大文字小文字と重複をまとめてから数え、1 回に 1,000 件までです (超えると 400)。results は GUID ごとの結果、missing は見つからなかった GUID、suggestions は含む GUID の多い順に並べた Release の候補 (最大 20 件) です。 結果はキャッシュされません。

{ "guids": ["0123456789abcdef0123456789abcdef", "…"] }
{
  "results": {
    "0123456789abcdef0123456789abcdef": { /* GET /assets/{guid} と同じ形 */ }
  },
  "missing": ["fedcba9876543210fedcba9876543210"],
  "suggestions": [
    { "release": { /* Release */ }, "matched": 42 }
  ]
}

パッケージと Release

GET /packages?q=

パッケージを名前・説明・識別子で検索します。最大 50 件を返します。q がなければ、最近更新された順です。

{
  "items": [
    {
      "id": "clx0package",
      "name": "VRChat SDK - Base",
      "description": null,
      "latestVersion": "3.7.0",
      "verifiedReleaseCount": 3
    }
  ]
}

GET /packages/{id}

パッケージの名前・説明・識別子と、Release の一覧を返します。 識別子の provider は package (パッケージ名)、booth (BOOTH の商品 ID)、url のいずれかです。

{
  "id": "clx0package",
  "name": "VRChat SDK - Base",
  "description": null,
  "identifiers": [{ "provider": "package", "value": "com.vrchat.base" }],
  "releases": [ /* Release の配列 */ ],
  "updatedAt": "2026-11-01T00:00:00.000Z"
}

GET /releases/{id}

Release を 1 つ返します。形は上の Release と同じです。

GET /releases/{id}/assets?cursor=&path=

Release に含まれるアセットを、パスの順に 100 件ずつ返します。nextCursor を次のリクエストの cursor に渡すと続きが読めます。最後のページでは null です。path を渡すと、そのパスで始まるものだけに絞り込みます。

{
  "items": [
    { "guid": "0123456789abcdef0123456789abcdef", "path": "Packages/com.vrchat.base/Runtime/Foo.cs", "type": "MonoScript" }
  ],
  "nextCursor": "…"
}

マニフェストを登録する

POST /submissions

ログインが必要です。ボディは、Unity 拡張が書き出すマニフェストのファイルと同じ形式です。

{
  "format": "petrichor/v1",
  "package": { "identifier": { "provider": "package", "value": "com.vrchat.base" }, "name": "VRChat SDK - Base" },
  "version": "3.7.0",
  "assets": [
    { "guid": "0123456789abcdef0123456789abcdef", "path": "Packages/com.vrchat.base/Runtime/Foo.cs", "type": "MonoScript" }
  ]
}
  • format は "petrichor/v1" 固定です。
  • version は 1〜64 文字のラベルです。
  • assets は 1〜30,000 件です。path は 1〜1,024 文字、type (例: MonoScript) は省略できます。
  • package は次のいずれかの形で指定します。識別子で指定した場合、同じ識別子のパッケージがあればそれに、なければ name で新しく作ります。
// 既存のパッケージの ID を指定する
{ "id": "clx0package" }

// 識別子を指定する。同じ識別子のパッケージがなければ、name で新しく作る
{ "identifier": { "provider": "package" | "booth" | "url", "value": "com.vrchat.base" },
  "name": "VRChat SDK - Base", "description": "…" }

// 識別子なしで、新しいパッケージを作る
{ "name": "…", "description": "…" }

正規化のルール

  • GUID は小文字にし、^[a-f0-9]{32}$ に合わないものは拒否します。
  • パスは \ を / に置き換えるだけで、大文字小文字や前後の空白は変えません。U+0000〜U+001F と U+007F の制御文字を含むパスは拒否します。
  • 同じ GUID に異なるパスがあれば拒否します。(GUID, パス) が同じ行は 1 つにまとめます。
  • type は Manifest の同定に含めません。最初に書かれた値を使います。

レスポンス

新しく登録されたときは 201、同じユーザーが同じ Release に登録済みのときは何もせず 200 で、どちらも次の形です。verified は、異なる 2 人以上が登録した (または Legacy の) Release で true になります。

{
  "releaseId": "clx0release",
  "manifestId": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "verified": false,
  "submitterCount": 1
}

上限

1 人あたり 1 日 100 件までです。UTC の日付で数え、登録のルールに通ったリクエストは、同じ内容の再送でも 1 件に数えます。超えると 429 を返します。

自分の登録

GET /me/submissions

ログインしているユーザーの登録を、新しい順に最大 100 件返します。source は WEB、API、LEGACY のいずれかです。ボディは配列そのものです。

[
  {
    "id": "clx0submission",
    "version": "3.7.0",
    "source": "API",
    "createdAt": "2026-11-01T00:00:00.000Z",
    "release": { /* Release */ }
  }
]

Bearer トークンで登録する

登録と自分の登録の一覧は、サイトのログイン (Cookie) のほか、Egeria のアクセストークンを Authorization: Bearer ヘッダーで渡して使えます。 Cookie での登録は source: WEB、Bearer での登録は source: API として記録されます。

curl -X POST https://api.natsuneko.com/petrichor/v2/submissions \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data @petrichor-manifest.json

Manifest のハッシュ

Manifest の ID は、正規化した (GUID, パス) を次のように連結したものの SHA-256 を、16 進数 (小文字) で表したものです。

  1. (GUID, パス) を、UTF-8 のバイト列として GUID、パスの順に比較して並べる
  2. 各行を ${guid}\t${path}\n (タブ区切りで、末尾は改行) にして、すべてつなげる
  3. つなげた文字列を UTF-8 で SHA-256 にかける

並べ替えは UTF-16 の順 (JavaScript の文字列比較) ではなく、UTF-8 のバイト順です。入力の順序には依存しません。ハッシュはサーバーが計算するので、リクエストに含める必要はありません。

import { createHash } from "node:crypto";

const manifestId = (assets: { guid: string; path: string }[]) => {
  const bytes = (text: string) => Buffer.from(text, "utf8");
  const sorted = [...assets].sort(
    (a, b) => Buffer.compare(bytes(a.guid), bytes(b.guid)) || Buffer.compare(bytes(a.path), bytes(b.path)),
  );
  const text = sorted.map((asset) => `${asset.guid}\t${asset.path}\n`).join("");
  return createHash("sha256").update(text, "utf8").digest("hex");
};

エラー

ステータス主な原因
400リクエストの形が正しくない、GUID の形式が正しくない、同じ GUID に異なるパスがある、パスに制御文字がある、新しいパッケージに名前がない、一括照合の GUID が多すぎる
401登録・自分の登録の一覧で、ログインしていない
404指定した GUID・パッケージ・Release が存在しない (登録では、id で指定したパッケージがない)
4291 日の登録数の上限を超えた

ボディの形は次のとおりです。メッセージは日本語で返ります。

// 登録のルールに反した
{ "error": { "message": "同じ GUID に異なるパスが指定されています", "code": 400 } }

// リクエストの形が違う
{ "message": "validation failed" }

// ログインしていない
{ "message": "unauthorized" }

v1

  • GET /petrichor/v1/asset/by/guid/{guid} は、GUID が登録されていれば 200 で { "id": "<guid>" }、なければ 404 を返します。
  • POST /petrichor/v1/submit は終了していて、常に 410 を返します。v2 の登録を使ってください。