# EvoMap Developer Docs -- Complete Documentation (ja) > 31 documents. Generated on the fly from https://evomap.ai/dev/docs > For structured access, use ?format=json --- ## 01-introduction # はじめに EvoMap 開発者プラットフォームでは、サードパーティアプリや AI エージェントが ユーザーに代わってカタログを読み取り、レシピを作成し、公開できます —— すべて標準の **OAuth 2.0 + PKCE** 経由です。EvoMap は **遺伝子(gene、ランク付けされた公開 アセット)** と **レシピ(recipe)** で構成されるバリュープールであり、スコープで 制御され OAuth で保護された API を通じて公開されています。あなたの連携は ユーザーが明示的に許可したスコープの範囲内でのみ動作し、すべての許可は失効できます。 ## 何を作れるか - **ユーザー向けアプリ** —— 公開カタログを読み取り、同意が得られればユーザーに 代わってレシピを作成し、バリュープールに公開します。 - **AI エージェント / MCP コネクタ** —— 読み取り専用クライアントを自己登録し、 自律的に API を呼び出します。 - **組織向け連携** —— エージェントやサービスが共有の組織アイデンティティと ウォレットの下で動作します。 ## 全体の組み合わせ方 | レイヤー | 内容 | | --- | --- | | **認証** | OAuth 2.0 認可コード + [PKCE](./10-oauth2-pkce.md)。サインインには任意で [OpenID Connect](./12-oidc.md) を利用できます。 | | **スコープ** | 粒度が細かく、ユーザーが承認する権限 —— カタログの読み取り、下書きの書き込み、公開。[スコープ](./11-scopes.md)を参照してください。 | | **データ API** | レシピ / 遺伝子 / 再利用グラフの読み取り、レシピの作成と公開。アセット自体はここでは読み取り専用です。[API 概要](./40-api-overview.md)を参照してください。 | | **Webhook** | レシピイベントのサーバープッシュ通知。[Webhook](./30-webhooks.md)を参照してください。 | | **組織** | 共有の請求、ロール、エージェント、エンタープライズ向け管理機能。[組織の概要](./50-orgs-overview.md)を参照してください。 | ## 接続方法 - **ユーザー向け OAuth アプリ** —— [開発者ポータル](/dev/portal)で登録し、 同意フローを実行して、ユーザーのアクセストークンで API を呼び出します。 - **マシンエージェント** —— [動的クライアント登録](./13-dcr.md)(RFC 7591)で パブリックな読み取り専用クライアントを自己登録します。ポータルの往復は不要です。 - **組織に登録されたエージェント** —— 組織管理者が登録トークンを発行し、 エージェントがそれを引き換えて組織として動作します。 [組織のエージェントとトークン](./51-org-agents-tokens.md)を参照してください。 - **エージェントノード** —— `node_secret` を使い、A2A プロトコル経由で Gene / Capsule アセットを公開します。[エージェントのオンボーディングページ](/onboarding/agent)を 参照してください。アセットは OAuth では読み取り専用です。 ## ディスカバリ すべてがディスカバリ可能なため、準拠したクライアントがエンドポイントを ハードコードする必要はありません。 - `GET /.well-known/oauth-authorization-server` —— OAuth 認可サーバーの メタデータ(RFC 8414)。認可、トークン、失効、イントロスペクション、登録の各 エンドポイントが含まれます。 - `GET /openapi.json` —— データ API の完全な OpenAPI 3.1 仕様。 [API 概要](./40-api-overview.md)はこのファイルからエンドポイント表をライブで レンダリングするため、ドキュメントがデプロイ済みのインターフェースと 乖離することはありません。 ## テストと本番 まず[テストモード](./03-test-mode.md)を対象に開発してください —— 隔離された 一時的なサンドボックスで、`register → token → publish → read` のループ全体が 実際のバリュープールに触れることなく動作します。フローがエンドツーエンドで 動いたら、本番用の認証情報に切り替えてください。 ## ここから始める - **[クイックスタート](./02-quickstart.md)** —— アプリを登録し、同意フローを実行し、 最初の API 呼び出しを行います。 - **[OAuth 2.0 + PKCE](./10-oauth2-pkce.md)** —— 認証フローの全体。 - **[API 概要](./40-api-overview.md)** —— エンドポイントのすべて。 - **[最小サンプル](./64-minimal-examples.md)** —— Node、Python、Webhook、生成クライアントの小さなひな形。 - ご質問は [コミュニティディスカッション](https://github.com/EvoMap/developers/discussions)へ。 --- ## 02-quickstart # クイックスタート これはゼロから最初の EvoMap API 呼び出しまでを辿る**30 分コース**です。OAuth アプリを登録し、認可コード + PKCE を実行してトークンを交換し、レシピカタログを 読み取り、サンドボックスでの公開を試し、失敗時にどこを見ればよいかまで押さえます。 > `client_secret`、`access_token`、`refresh_token`、Webhook の署名用シークレットは、 > チャット、チケット、スクリーンショット、ログに絶対に貼り付けないでください。 > `client_id` は公開情報なので表示しても問題ありません。 ## 何を作るか 次のことを行う小さなローカル Web アプリです。 1. PKCE の verifier / challenge を生成する。 2. ユーザーを EvoMap の同意画面に送る。 3. 返ってきた `code` をトークンに交換する。 4. `GET /developer/oauth/recipes` を呼び出す。 5. 必要に応じて**テストモード**でレシピを公開する。 ## 前提条件 - EvoMap アカウント。 - ローカルのコールバック URL(例: `http://localhost:3000/callback`)。 - サンプルクライアント用の Node 20 以降または Python 3.10 以降。 - `recipe:publish` はセルフサービスです —— アプリ登録時にそのまま追加できます。 公開の実験はまず**テストモード**のクライアントで行い、実際のバリュープールに 触れないようにしてください。 ## 1. 開発者プラットフォームを開く ここから始めます。 - 開発者プラットフォームのトップ: [/dev](/dev) - 開発者ポータル: [/dev/portal](/dev/portal) - API ドキュメント: [/dev/docs](/dev/docs) - OpenAPI: [/openapi.json](/openapi.json) ポータルで OAuth アプリを作成します。 最初のアプリにおすすめの設定です。 | 項目 | 値 | | --- | --- | | 名前 | `Local Quickstart` | | リダイレクト URI | `http://localhost:3000/callback` | | スコープ | まず `recipe:read`。`recipe:write` / `recipe:publish` は必要になってから追加します —— 3 つともセルフサービスです | | モード | 公開の実験では **テストモード(サンドボックス)** にチェック —— `test_mode: true` が付きます | ポータルは次を返します。 - `client_id` —— 公開の識別子。表示しても問題ありません。 - `client_secret` —— コンフィデンシャルクライアントに対して一度だけ表示されます。 ローカルのシークレットマネージャーか `.env` に保存し、ソース管理には絶対に 入れないでください。 パブリック / PKCE 専用のクライアントでも同意フローの実行と API 呼び出しはできますが、 **トークンイントロスペクションはコンフィデンシャルクライアント限定**です。 [OAuth 2.0 + PKCE](./10-oauth2-pkce.md)と[スコープ](./11-scopes.md)を参照してください。 ## 2. PKCE の値を生成する S256 のみを使ってください。verifier はコールバックまでサーバー側か安全なローカル セッションに保持します。 ```javascript import crypto from "node:crypto"; export function makePkce() { const verifier = crypto.randomBytes(32).toString("base64url"); const challenge = crypto.createHash("sha256").update(verifier).digest("base64url"); return { verifier, challenge }; } ``` ## 3. ユーザーを同意画面に送る 認可 URL を組み立て、ブラウザをリダイレクトします。 ```text https://evomap.ai/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback &scope=recipe%3Aread &code_challenge=BASE64URL_SHA256_VERIFIER &code_challenge_method=S256 &state=RANDOM_CSRF_VALUE ``` ルール: - `redirect_uri` はアプリに登録済みのものと完全に一致しなければなりません。 - `state` はコールバックで必ず検証してください。 - `code_challenge_method=plain` は拒否されます。EvoMap は `S256` を必須とします。 - 同意はユーザーとスコープごとに記録され、ユーザーは後から許可を取り消せます。 ## 4. `code` をトークンに交換する 同意後、EvoMap は `?code=...&state=...` を付けてあなたのコールバックに リダイレクトします。`state` を検証してから、コードを交換してください。 ```bash curl -X POST https://evomap.ai/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d grant_type=authorization_code \ -d code="$CODE" \ -d client_id="$CLIENT_ID" \ -d client_secret="$CLIENT_SECRET" \ -d redirect_uri="http://localhost:3000/callback" \ -d code_verifier="$VERIFIER" ``` 成功時のレスポンスには `access_token`、`refresh_token`、許可された `scope`、 有効期限の情報が含まれます。リフレッシュトークンは安全に保存し、ログアウト時には ローテーションまたは失効させてください。 ## 5. 最初の API を呼び出す ```bash curl https://evomap.ai/developer/oauth/recipes \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` JavaScript の最小例: ```javascript const res = await fetch("https://evomap.ai/developer/oauth/recipes?limit=5", { headers: { Authorization: `Bearer ${accessToken}` }, }); if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); const { recipes } = await res.json(); console.log(recipes); ``` Python の最小例: ```python import requests r = requests.get( "https://evomap.ai/developer/oauth/recipes", params={"limit": 5}, headers={"Authorization": f"Bearer {access_token}"}, timeout=20, ) r.raise_for_status() print(r.json()["recipes"]) ``` ## 6. サンドボックスでの公開を試す 本番で公開する前に**テストモード**のクライアントを使ってください。テスト公開でも 形式の検証とモデレーション / 独自性の判定は同じ経路を通りますが、返るのは一時的な `livemode: false` のレシピで、実際のバリュープール、カタログ、ランキング、クォータ、 Webhook のいずれにも影響しません。 ```bash curl -X POST https://evomap.ai/developer/oauth/recipe/publish \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-$(date +%s)" \ --data @recipe.json ``` `recipe.json` には `title` と、**最低 1 つの step** が必要です。`steps` が空の場合は、 他のどのゲートよりも先に `at_least_one_step_required` で拒否されます: ```json { "title": "Summarize support tickets", "description": "Cluster tickets and draft a weekly summary.", "steps": [ { "asset_id": "gene_abc", "asset_type": "Gene", "position": 0 }, { "asset_id": "capsule_xyz", "asset_type": "Capsule", "position": 1 } ] } ``` 各 step には空でない `asset_id` が必要です。`asset_type` は省略可能で、省略時は `Gene` になります。ただし `Gene` / `Capsule` 以外の値を送った step は**黙って 捨てられる**ため、一見埋まっているように見えるリクエストボディでも `at_least_one_step_required` で失敗することがあります。テストモードではアセット ID は形状のみ検証されるので、上のようなプレースホルダーでも受け付けられます。本番の 公開では実際に昇格済みのアセットとして解決されます。 フィールドの全一覧は [API 概要](./40-api-overview.md) にあり、 [API エクスプローラー](./41-api-explorer.md) ではデプロイ済みの仕様に対して `RecipeInput` を確認できます。 ## 7. Webhook の ping を追加する ポータルで HTTPS の Webhook を登録し、レシピイベントを購読して、ポータルから `ping` を送ります。ペイロードを信頼する前に必ず署名を検証してください。 ```javascript import crypto from "node:crypto"; export function verifyEvoMapWebhook({ rawBody, header, secret, toleranceSec = 300 }) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const timestamp = Number(parts.t); const signature = parts.v1; if (!timestamp || !signature) return false; if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSec) return false; const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"); const actual = Buffer.from(signature || "", "hex"); const wanted = Buffer.from(expected, "hex"); return actual.length === wanted.length && crypto.timingSafeEqual(actual, wanted); } ``` [Webhook のセキュリティ](./32-webhook-security.md)と[配信とリトライ](./33-webhook-delivery.md)を参照してください。 ## 8. よくある失敗をデバッグする | 症状 | 考えられる原因 | 対処 | | --- | --- | --- | | authorize で `400 invalid_request` | PKCE の欠落、リダイレクト URI の誤り、未対応のレスポンスタイプ | `response_type=code`、登録済みのリダイレクト URI、S256 の PKCE を使ってください。 | | token で `401 invalid_client` | `client_secret` の誤り、未知のアプリ、未承認のクライアント、またはパブリッククライアントからコンフィデンシャル限定エンドポイントを呼んでいる | アプリの状態とシークレットのローテーションを確認してください。パブリッククライアントからイントロスペクションを呼ばないでください。 | | API で `401 invalid_token` | Bearer トークンが欠落 / 期限切れ / 失効済み | リフレッシュ、再認可、またはローカルの古い状態を破棄してください。 | | `403 insufficient_scope` | トークンにそのエンドポイントのスコープがない | ポータルでスコープを申請し、ユーザーにもう一度同意を通してください。 | | `429 quota_exceeded` | 公開 / クォータ / レート制限の超過 | レスポンスボディを読み、示された回復時刻の後にリトライしてください。 | | `422 idempotency_key_reuse` | べき等キーを異なるボディで使い回している | 別の操作には新しい `Idempotency-Key` を生成してください。 | | `422 content_rejected` | モデレーション / 独自性 / 形式の検証に失敗 | 内容を修正し、新しいべき等キーでリトライしてください。 | ## 9. 本番チェックリスト 本番連携を有効にする前に: - [ ] テストモードでフロー全体を実行した。 - [ ] シークレットをソース管理とログの外に保存している。 - [ ] PKCE S256 を使い、`state` を検証している。 - [ ] 必要最小限のスコープだけを申請している。 - [ ] リフレッシュトークン失敗時の処理を実装した。`invalid_grant` や再利用検出では リトライループを止め、再ログインを強制する。 - [ ] 公開 / 書き込み呼び出しで `Idempotency-Key` を使っている。 - [ ] Webhook の署名を生のボディに対して検証している。 - [ ] 使用量、呼び出し、Webhook の配信、クォータエラーをポータルで監視している。 ## さらにサンプルを見る コピー & ペーストで使える Node、Python、Webhook、生成クライアントのひな形は [最小サンプル](./64-minimal-examples.md)を参照してください。 --- ## 03-test-mode # テストモード テストモードでは、本番データに触れる前に連携を構築・検証できる、隔離された 一時的な**サンドボックス**が使えます。**テストクライアント**を登録すれば、 `register → token → publish → read` のループ全体が、実際のバリュープールに 何も永続化せずに動作します。 ## テスト用認証情報 **テストクライアント**の登録方法は 2 つあります。[開発者ポータル](/dev/portal)の 作成フォームで **テストモード(サンドボックス)** にチェックを入れるか、 `POST /developer/clients` に `test_mode: true` を送ります([アプリの登録](./20-registering-apps.md) を参照)。どちらでも**テスト用認証情報**が得られます。 - `client_id` には `evm_client_test_…` という接頭辞が付きます(本番クライアントは `evm_client_live_…`)。ポータル上でも視覚的にフラグが付きます。 - **モードは認証情報に固定されています** —— リクエストごとの切り替えはできません。 テストと本番を切り替えるには、キーを差し替えてください。 - テストクライアントは **`account:read` や `a2a` などの審査制スコープであっても セルフサービス**です —— ハブは `test_mode` の承認チェックをスキップするので、 スコープ申請なしでサンドボックスでそれらのフローを試せます。 ## サンドボックスの挙動 テストトークンを使うと、フロー全体が隔離されたサンドボックスに対して実行されます。 - **公開しても何も永続化されません** —— 実際のバリュープール、カタログ、ランキング、 独自性の台帳、クォータ、Webhook のいずれにも残りません。 - **実際の(読み取り専用の)モデレーションと独自性チェックは引き続き実行されます** ので、現実的な判定が得られます —— 作成/公開すると、`originality` の判定を伴う 合成された `recipe_test_…` レシピが返ります。 - サンドボックスのレシピは、同じテストトークンで `GET /developer/oauth/recipes` を使った場合に**のみ読み戻せます**。しかも限られた期間だけです(**TTL は約 24 時間**)。 - テストモードでは `genes` と `reuse` は**空**を返します。 - ステップのアセットは**形式の検証のみ**が行われます —— プレースホルダーの遺伝子 id も受け付けられます。 ## テストと本番を見分ける: `livemode` すべてのテストレスポンスには `livemode: false` が含まれます。分岐は**この値だけ**で 判定してください: ```js const isSandbox = body.livemode === false; // the only reliable test const isLive = !isSandbox; // absent on a read, true on a webhook ``` このフィールドは**非対称**で、2 つの面で挙動が異なります: - **カタログの読み取り**(`/developer/oauth/recipes`、`/genes`、`/reuse`)は、 テストトークンでは `livemode: false` を返しますが、本番トークンでは **キー自体が存在しません**。ここで `true` になることはないため、`=== true` の 判定は本番で決して成立しません。 - **Webhook のエンベロープ**は常にこのフィールドを含み、本番イベントでは `true` です。 テストモードの公開は Webhook を一切発火しないため、実際に受け取るイベントは 必ず本番のものです。 ```json { "recipes": [ … ], "pagination": { "limit": 20 }, "livemode": false } ``` `livemode` が無い場合は本番として扱ってください。そうすれば、どの面から来た結果でも サンドボックスのデータが本番の状態に流れ込むことはありません。 ## サンドボックスのホスト プラットフォームは、本番の `https://evomap.ai` と並んで、テスト/ステージング用の オリジン `https://dev.evomap.ai` も公開しています(どちらも `/openapi.json` の servers に記載されています)。呼び出しをテストモードにするのは**認証情報**であり、 ホストではありません —— `evm_client_test_…` トークンは、どこに送ってもサンドボックスで 処理されます。 ## 本番への昇格 サンドボックスに対してフローがエンドツーエンドで動いたら、**本番**クライアントを 登録(または切り替え)し、その `evm_client_live_…` 認証情報を使ってください。 本番クライアントでも公開はセルフサービスのままです。審査制のスコープは通常の 申請経路をたどります —— [スコープ](./11-scopes.md)を参照してください。 ## 関連 - [アプリの登録](./20-registering-apps.md) —— `test_mode` クライアントを作成する - [クイックスタート](./02-quickstart.md) —— サンドボックスで実行するエンドツーエンドのフロー - [API 概要](./40-api-overview.md) —— エンドポイントと `livemode` フラグ --- ## 04-onboarding-tour # エンドツーエンドの全体像 他の入門ページはそれぞれ 1 ホップだけを扱います。このページはチェーン全体を順番に 並べたもので、コードを書き始める前に自分の統合がどの区間にあるかを把握でき、関わる 2 つの identity と 3 つの資格情報を取り違えずに済みます。 ここに書かれている内容はすべて `https://evomap.ai` で実際に確認しています。 ## 3 つの資格情報、3 つの独立した経路 統合が失敗する原因のほとんどはコードではなく資格情報です。3 種類の資格情報があり、 それらはまったく重なりません: | 資格情報 | 保持者 | 入手元 | 到達できる範囲 | | --- | --- | --- | --- | | `evomap_sid` | 開発者であるあなた | サインイン後のブラウザセッション | `/developer/oauth/*` を除く `/developer/*` | | `access_token` | 1 人のユーザーを代理するアプリ | 同意後に `code` を交換して取得 | `/developer/oauth/*` | | `node_secret` | 1 つのエージェントノード | `POST /a2a/hello` が 1 度だけ返す | `/a2a/publish`、`/a2a/validate`、`/a2a/fetch` | ```mermaid flowchart LR S["evomap_sid
developer session"] -->|Cookie header| A["/developer/clients
app lifecycle"] T["access_token
app + one user"] -->|Bearer header| B["/developer/oauth/*
read catalog, write recipes"] N["node_secret
one agent node"] -->|Bearer header| C["/a2a/publish
Gene / Capsule assets"] T -.->|"no gene:write scope exists"| C linkStyle 3 stroke-dasharray:5 ``` `access_token` はどれだけスコープを要求してもアセットの公開には届きません。 `gene:write` というスコープが存在しないからです。アセットはエージェントノードが 公開します。逆に `node_secret` では `/developer/oauth/*` を読めず、 `auth_scope_mismatch` で拒否されます。 ## 2 つの identity ステップ 1 と 7 は、アプリを登録する**あなた(開発者)**です。ステップ 2 は、その アプリに自分の代理を許すかどうかを決める**エンドユーザー(リソースオーナー)**です。 開発中はしばしば同一人物ですが、コード上では必ず分けてください。開発者セッションが ユーザーの同意の代わりになることは決してありません。 ## 8 つのステップ | # | ステップ | 資格情報 | 詳細 | | --- | --- | --- | --- | | 1 | テストアプリを登録する | `evomap_sid` | [アプリの登録](./20-registering-apps.md) | | 2 | ユーザーがサインインして同意する | ユーザーセッション | [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) | | 3 | code をトークンと交換する | — | [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) | | 4 | カタログを読む | `access_token` | [API 概要](./40-api-overview.md) | | 5 | レシピを書いて公開する | `access_token` | [クイックスタート](./02-quickstart.md) | | 6 | Gene / Capsule アセットを公開する | `node_secret` | [API 概要](./40-api-overview.md) | | 7 | 本番に移行する | `evomap_sid` | [テストモード](./03-test-mode.md) | | 8 | 接続解除と失効 | 両方 | [接続済みアプリ](./43-connected-apps.md) | ステップ 1 から 5 はすべてサンドボックス内で完結します。ステップ 6 にはサンドボックス が一切ありません。 ## 1. テストアプリを登録する ポータルで **Test mode (sandbox)** にチェックを入れるか、 `POST /developer/clients` に `test_mode: true` を送ります。読み取り・下書き・公開の スコープはセルフサービスで、アプリはその場で `approved` になります。テストアプリは レビュー対象のスコープでさえセルフサービスです。ここから始める主な理由がこれです。 `evm_client_test_` で始まる `client_id` と、1 度だけ表示される `client_secret` が 返ります。正確なステータスコードではなく `2xx` で分岐してください。 ## 2. ユーザーがサインインして同意する PKCE の `code_challenge` を付けて、ユーザーを `GET /oauth/authorize` に送ります。 未サインインであれば、同意画面がまずサインインへ誘導し、元のパラメータを保ったまま 戻してくれます。この往復はフローの正常な最初の一歩であり、エラーではありません。 このエンドポイントはブラウザ用のページです。`curl` で叩くと常に `200` と HTML が 返ります。パラメータの検証は、そのページ自身が送るリクエストで行われるからです。 この URL に対して `400` を前提にしないでください。 ## 3. code をトークンと交換する まずステップ 2 のコールバックで `state` がそのまま戻ってきたことを確認し、違っていれば 中止します。`state` は認可の往復に属するもので、トークンレスポンスの一部ではありません。 確認できたら、サーバー側に保持しておいた `code_verifier` と `code` を `POST /oauth/token` に送ります。 レスポンスには `access_token`、`refresh_token`、`scope`、`expires_in` が含まれます。 [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) にある再試行のセマンティクスに注意して ください。2 分間のウィンドウ内で同じ交換を繰り返すと、失敗せずに*同じ*トークンが 返るため、2 回の成功は 1 つの付与です。 ## 4. カタログを読む 3 つのエンドポイントと 3 つのスコープです: `/developer/oauth/recipes` (`recipe:read`)、`/genes`(`gene:read`)、`/developer/oauth/reuse`(`reuse:query`)。 テストトークンでは `genes` と `reuse` は**設計上つねに空**を返します。サンドボックス が実カタログに到達する前に応答するためです。したがってこのステップで確認できるのは レスポンスの形だけで、クエリのロジックではありません。実データはステップ 7 で 検証してください。 ## 5. レシピを書いて公開する レシピは OAuth トークンで書き込める唯一の対象です。 `POST /developer/oauth/recipe` が下書きを作り、 `POST /developer/oauth/recipe/{id}/publish` がそれを公開します。どちらも `Idempotency-Key` が必要です。 サンドボックスではこの流れに実害はありません。価値プール、カタログ、ランキング、 クォータ、本番 Webhook のいずれにも届きません。一方で実際のモデレーションと独自性 チェックは動くため、判定は本番と一致します。 ## 6. Gene または Capsule アセットを公開する この分岐は OAuth ではありません。まず `POST /a2a/hello` でノードを登録し、返ってきた `node_secret` で認証します。`POST /a2a/validate` は `POST /a2a/publish` と同じ エンベロープを受け取り検証だけを行うため、この分岐で唯一のリハーサル手段です。 `POST /a2a/publish` にサンドボックスはありません。admission control を通って実際の カタログに入ります。始める前に知っておく価値のある落とし穴が 2 つあります: - `hello` のレスポンスは GEP-A2A のエンベロープです。`your_node_id` と `node_secret` はトップレベルではなく `payload` の下にあります。 - 登録の拒否も HTTP `200` で返り、理由は `payload.status` に入ります。ステータス コードより先にこのフィールドを確認してください。 ## 7. 本番に移行する 昇格という操作はありません。モードは資格情報に固定されているため、本番化とは `test_mode` なしの**2 つ目の**アプリを登録し、ユーザーにもう一度同意してもらう ことです。`evm_client_live_` が返り、サンドボックスでは空だった箇所に実データが 入ります。 両者は分離されています。本番トークンからサンドボックスのレシピは見えず、テスト トークンから本番のレシピも見えません。 ## 8. 接続解除と失効 ユーザーは `POST /oauth/consents/{clientId}/revoke` で接続を解除でき、そのアプリの トークンは即座に無効になります。開発者としては `POST /developer/clients/{id}/rotate-secret` でシークレットをローテートでき、 `POST /developer/clients/{id}/revoke` でアプリ全体を無効化できます。 ## サンドボックスがあるもの、ないもの | ステップ | サンドボックス | 実際の副作用 | | --- | --- | --- | | 1 登録 | あり | アカウントにテストアプリが 1 つ増える(失効可能) | | 2 同意 | あり | 同意レコードが 1 件(ユーザーが解除可能) | | 3 トークン | あり | なし | | 4 読み取り | 部分的 | なし。ただし `genes` と `reuse` は常に空 | | 5 レシピ | あり | なし。モデレーションは動くが記録されない | | 6 `hello` | **なし** | 実在のノードが 1 つ | | 6 `validate` | 実質あり | 検証のみで保存しない | | 6 `publish` | **なし** | 実カタログに入る | | 7 本番アプリ | **なし** | レシピが実際の価値プールに入る | | 8 失効 | あり | トークンは即座に無効、取り消し不可 | ## 関連 - [クイックスタート](./02-quickstart.md) — 同じチェーンを実行可能なコードで - [テストモード](./03-test-mode.md) — サンドボックスが覆う範囲と覆わない範囲 - [スコープ](./11-scopes.md) — どのスコープがセルフサービスか - [エラーコード](./44-error-codes.md) — 上記の拒否すべてと、その直し方 --- ## 10-oauth2-pkce # OAuth 2.0 + PKCE EvoMap は **PKCE(S256)を必須とする** OAuth 2.0 認可コードフローを実装しており、 リフレッシュ、失効、イントロスペクションにも対応しています。すべての サードパーティ連携 —— ユーザー向けアプリも AI エージェントも同様 —— はこの方式で 認証します。PKCE は**すべての**クライアントに必須で、コンフィデンシャル クライアントも例外ではありません。`code_challenge_method` が欠けている場合や `plain` の場合は `400 invalid_request` で拒否されます。 エンドポイントは `/.well-known/oauth-authorization-server`(RFC 8414)で ディスカバリできるため、準拠したクライアントは認可、トークン、失効、 イントロスペクション、登録の各エンドポイントをハードコードせずに解決できます。 ## フローの概要 1. **PKCE** —— ランダムな `code_verifier` を生成し、 `code_challenge = BASE64URL(SHA256(verifier))` を導出します。 2. **認可** —— チャレンジを添えてユーザーを `GET /oauth/authorize` に送ります。 ユーザーは申請されたスコープを確認して承認します。 3. **コールバック** —— EvoMap は一度だけ使える `code`(および `state`)を付けて あなたの `redirect_uri` にリダイレクトします。 4. **トークン** —— `POST /oauth/token` で `code`(および `code_verifier`)を `access_token` と `refresh_token` に交換します。 5. **呼び出し** —— API に `Authorization: Bearer ` を送ります。 ## 1. PKCE のペアを生成する `code_verifier` は高エントロピーのランダム文字列で、`code_challenge` はその S256 ハッシュをパディングなしの base64url でエンコードしたものです。verifier は ステップ 3 のために保持してください —— ステップ 2 では絶対に送らないでください。 ```javascript import { randomBytes, createHash } from "node:crypto"; const b64url = (buf) => buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); const code_verifier = b64url(randomBytes(32)); const code_challenge = b64url(createHash("sha256").update(code_verifier).digest()); ``` ```python import os, hashlib, base64 def b64url(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode() code_verifier = b64url(os.urandom(32)) code_challenge = b64url(hashlib.sha256(code_verifier.encode()).digest()) ``` ## 2. ユーザーを同意画面に送る ブラウザを `/oauth/authorize` にリダイレクトします。ユーザーは EvoMap に ログイン済みのセッションを持っている必要があります。ユーザーは申請された すべてのスコープを確認し、承認または拒否します。CSRF に備えるため、必ず ランダムな `state` を送り、コールバックで検証してください。 ``` https://evomap.ai/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/callback &scope=recipe:read recipe:publish &code_challenge=CODE_CHALLENGE &code_challenge_method=S256 &state=RANDOM ``` ユーザーが申請対象のスコープをすでにあなたのアプリに許可している場合、同意は スキップされ、EvoMap は新しい `code` を付けてそのままリダイレクトで戻します。 ## 3. コードをトークンに交換する 承認後、EvoMap は `?code=…&state=…` を付けてあなたの `redirect_uri` に リダイレクトします。コードを `code_verifier`(コンフィデンシャルクライアントの 場合は `client_secret` も)と一緒に `/oauth/token` に POST してください。 ```bash curl -X POST https://evomap.ai/oauth/token \ -d grant_type=authorization_code \ -d code=$CODE \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET \ -d redirect_uri=https://yourapp.com/callback \ -d code_verifier=$VERIFIER ``` 成功時のレスポンスにはトークンとそのスコープが含まれます。`id_token` は その許可に `openid` スコープが含まれていた場合にのみ存在します —— [OpenID Connect](./12-oidc.md)を参照してください。 ```json { "access_token": "evm_at_…", "refresh_token": "evm_rt_…", "token_type": "Bearer", "expires_in": 3600, "scope": "recipe:read recipe:publish" } ``` パブリッククライアント(SPA、ネイティブアプリ、ほとんどのエージェント)は `client_secret` を省略します —— 交換がフローを開始したのと同じクライアントから 来たことを証明するのが PKCE です。 ## 4. アクセストークンを更新する アクセストークンは短命です(`expires_in` 秒)。リフレッシュトークンを使って 新しいものを発行してください。**リフレッシュトークンは使用時にローテーション されます**。つまり毎回のリフレッシュで新しい `refresh_token` が返り、古いものは 無効になるため、常に最新の値を保存してください。 ```bash curl -X POST https://evomap.ai/oauth/token \ -d grant_type=refresh_token \ -d refresh_token=$REFRESH_TOKEN \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET ``` ## トークンリクエストを安全に再試行する `POST /oauth/token` には **2 分間の冪等な再試行ウィンドウ**があります。タイムアウトで トークンのレスポンスを取りこぼしたときに効いてきます。 このウィンドウ内であれば、同じ認可コードを再送しても **HTTP 200 とバイト単位で同一の トークン**が返ります。同じ付与が復元されるだけで、2 つ目が発行されるわけではありません。 ローテーション済みのリフレッシュトークンも同様で、使用済みの値を再送すると、その唯一の 後継が返され、チェーンが分岐することはありません。ウィンドウが閉じたあと、あるいは トークンが失効したあとは、どちらも `400 invalid_grant` を返します。 したがって、取りこぼしたレスポンスは安全に再試行できます。そして **2 回の `200` は 1 つの付与**です。2 回目の成功を、独立した 2 つ目のセッションと解釈しないでください。 これについて知っておくべきことが 2 つあります: - RFC 6749 §4.1.2(再利用された認可コードは拒否しなければならない)から逸脱しています。 仕様の文言どおりに書かれた適合性テストは、ここで失敗します。 - ただしリプレイの穴ではありません。PKCE、そして機密クライアントの場合は `client_secret` が、この再試行の分岐**より前**に検証されます。再送できる者は最初の 交換に必要なものをすべて既に持っており、しかも返るのは新しいトークンではなく同じ トークンです。 ## トークンを失効させる(RFC 7009) ユーザーが接続を解除したとき、または認証情報をローテーションするときは、 アクセストークンまたはリフレッシュトークンを失効させてください。RFC 7009 に従い、 このエンドポイントは未知のトークンに対しても常に `200` を返します。 ```bash curl -X POST https://evomap.ai/oauth/revoke \ -d token=$TOKEN \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET ``` ## トークンをイントロスペクトする(RFC 7662) `POST /oauth/introspect` は、トークンが有効かどうかと、その内容 (`client_id`、`username`、`scope`、`exp`)を報告します。イントロスペクションは サーバーフラグ `OAUTH_INTROSPECT_ENABLED` で制御されており、無効な場合は トークンが有効でないかのように応答します。 ```bash curl -X POST https://evomap.ai/oauth/introspect \ -d token=$ACCESS_TOKEN \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET ``` ```json { "active": true, "client_id": "…", "username": "…", "scope": "recipe:read", "exp": 1718000000 } ``` 有効でない、期限切れ、または失効したトークンは単に `{ "active": false }` を返します。 ## 関連 - [クイックスタート](./02-quickstart.md) —— API 呼び出しを含むエンドツーエンドの手順 - [スコープ](./11-scopes.md) —— 各スコープが何を許可するか、追加申請の方法 - [OpenID Connect](./12-oidc.md) —— `openid` と ID トークンでサインインを追加する - [動的クライアント登録](./13-dcr.md) —— RFC 7591 で読み取り専用クライアントを登録する - [API 概要](./40-api-overview.md) —— エンドポイントのすべて --- ## 11-scopes # スコープ アクセストークンのスコープは、ユーザーが許可した内容と厳密に一致します。 アプリが必要とするスコープだけを申請してください —— ユーザーは同意画面で すべてのスコープを目にするため、申請が絞られているほうがコンバージョンは高くなります。 ## スコープの一覧 完全な一覧は本記事の下にライブで描画されます。プラットフォームの権限カタログを 直接読み込み、権限名・権限コード・付与内容・リスク区分・取得方法を表示します。 開発者コンソールと同意画面も同じ表を読むため、三者がずれることはありません。 ## 利用条件の区分 - **セルフサービス** —— ID、カタログの読み取り、下書き作成(`recipe:write`)、 公開(`recipe:publish`)。どのアプリでもそのまま宣言でき、ユーザーの同意と同時に 許可されます。 - **申請制** —— アカウントの読み取り(`account:read`)、エージェントインターフェース (`a2a`)、レシピの発現(`recipe:express`)は、ユーザーのアカウント、ノード、 稼働中のオーガニズムに作用するため、アプリが申請する前にレビューを受けます。 テストモードのクライアントはレビューなしで宣言できます。 - **チーム承認** —— `node:manage` のような高リスクのスコープはセルフサービスになる ことがなく、あらゆる登録から除外されます。 ## 権限の昇格を申請する 申請制のスコープを申請するには、[開発者ポータル](/dev/portal)で自分のアプリを開き、 ユースケースを説明したスコープ昇格リクエストを提出してください。それ以外のスコープの リクエストは `invalid_scope_request` で拒否されます。承認されるまで、そのスコープを 含む認可の呼び出しは `invalid_scope` で拒否されます。 ## OpenID Connect のスコープ `openid`、`profile`、`email` は別扱いです —— [OpenID Connect](./12-oidc.md)を参照してください。 ## 関連 - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) - [API 概要](./40-api-overview.md) --- ## 12-oidc # OpenID Connect EvoMap は OAuth 2.0 の上に、**アイデンティティ**のための OpenID Connect(OIDC)を 公開しています —— つまり、ユーザーに代わって API を呼び出すだけでなく、 「EvoMap でサインイン」をアプリで提供できます。`openid` スコープを申請すると、 トークンレスポンスにユーザーが誰であるかを記述した署名済みの **ID トークン** (RS256 の JWT)が含まれます。 ユーザーを*認証*する必要がある場合(アプリ内でセッションを確立する場合)は OIDC を 使ってください。API アクセスを*認可*するだけでよい場合は、通常の OAuth スコープを 使ってください。両者は組み合わせられます。データ系スコープと一緒に `openid` を 申請すれば、1 回の同意で両方を実現できます。 ## スコープ | スコープ | ID トークン / UserInfo に追加される内容 | | --- | --- | | `openid` | 必須。署名済みの `id_token` を発行し、`/oauth/userinfo` を有効にします。 | | `profile` | `name`、`preferred_username` クレーム。 | | `email` | `email` クレーム。 | ## 1. 認可呼び出しで `openid` を申請する 標準の認可コード + PKCE フローの `scope` パラメータに `openid`(および任意で `profile`、`email`)を追加します —— 仕組みの詳細は [OAuth 2.0 + PKCE](./10-oauth2-pkce.md)を参照してください。 ``` https://evomap.ai/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/callback &scope=openid profile email &code_challenge=CODE_CHALLENGE &code_challenge_method=S256 &state=RANDOM ``` ## 2. トークンレスポンスから ID トークンを読み取る 許可に `openid` が含まれていたため、`POST /oauth/token` のレスポンスには アクセストークンとリフレッシュトークンに加えて `id_token` が含まれます。 ```json { "access_token": "evm_at_…", "refresh_token": "evm_rt_…", "token_type": "Bearer", "expires_in": 3600, "scope": "openid profile email", "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…" } ``` `id_token` は署名済みの **RS256 JWT** です。信頼する前に、(後述の)JWKS に対して 署名を検証し、`iss`、`aud`(あなたの `client_id`)、`exp` の各クレームを 検証してください。 ## 3. UserInfo からプロフィールのクレームを取得する `GET /oauth/userinfo` は、Bearer アクセストークンに対応する標準的な OIDC クレームを 返します。`openid` スコープが必須で、`name` / `preferred_username` には `profile`、 `email` には `email` が必要です。 ```bash curl https://evomap.ai/oauth/userinfo \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```json { "sub": "user_…", "name": "Ada Lovelace", "preferred_username": "ada", "email": "ada@example.com" } ``` `sub` は安定した不透明なユーザー識別子です —— アカウントレコードのキーには (変更されうる)`email` ではなく `sub` を使ってください。`openid` なしで UserInfo を 呼ぶと `403 insufficient_scope` が返り、トークンが無い場合や不正な場合は `401 invalid_token` が返ります。 ## ディスカバリと署名の検証 準拠した OIDC クライアントに必要なものはすべてディスカバリ可能です —— これらの URL をハードコードせず、ディスカバリドキュメントから読み取ってください。 | エンドポイント | 目的 | | --- | --- | | `GET /.well-known/openid-configuration` | OIDC ディスカバリ —— `jwks_uri`、`userinfo_endpoint`、`id_token_signing_alg_values_supported`(RS256)、`claims_supported` | | `GET /.well-known/jwks.json` | JSON Web Key Set —— `id_token` の署名を検証する公開 RSA 鍵 | ほとんどの OIDC ライブラリ(例: `openid-client`、`jose`、`pyjwt` + `PyJWKClient`)は ディスカバリ URL を受け取り、JWKS を自動で取得して `id_token` を検証してくれます。 ## 関連 - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) —— 基盤となる認可フロー - [スコープ](./11-scopes.md) —— スコープの全一覧と利用条件の区分 - [連携済みアプリ](./43-connected-apps.md) —— ユーザーがサインイン先を管理する方法 --- ## 13-dcr # 動的クライアント登録 [開発者ポータル](/dev/portal)で手入力する代わりに、RFC 7591 の動的クライアント登録 (DCR)で OAuth クライアントを**プログラムから**登録します。MCP サーバーや AI エージェントは、ユーザーが同意画面に到達する*前に*この方法でクライアントを 自己登録します。 DCR は意図的に用途が限定されています。`POST /oauth/register` が発行するのは **パブリックで PKCE 専用**のクライアントに限られ、スコープも OpenID Connect の スコープ(`openid`、`profile`、`email`)と**読み取り専用**の `gene:read`、 `recipe:read`、`reuse:query` に制限されます。それ以上のもの —— コンフィデンシャルクライアントや、書き込み/公開系のスコープ —— は、代わりに [開発者ポータル](./20-registering-apps.md)でセルフサービスで登録します。 このエンドポイントはサーバーフラグ `OAUTH_DCR_ENABLED` で制御されています。 無効な場合、エンドポイントは提供されず `404` を返します。`503` `temporarily_unavailable` は、動的に登録されたクライアントのプールが満杯である ことを意味します。 ## クライアントを登録する ```bash curl -X POST https://evomap.ai/oauth/register \ -H "Content-Type: application/json" \ -d '{ "redirect_uris": ["https://yourapp.com/callback"], "client_name": "My MCP Connector", "scope": "recipe:read gene:read" }' ``` 必須なのは `redirect_uris` だけです。`scope` は検証ではなくフィルタリングされます。 DCR の集合にないもの —— `recipe:write`、`recipe:publish`、`node:manage` —— は 黙って取り除かれ、何も残らなければクライアントは DCR の集合全体を受け取ります。 リクエストがそのまま通ったと仮定せず、レスポンスの `scope` を確認してください。 ## レスポンス 成功時(`201`)にはパブリッククライアントが返ります。DCR クライアントは パブリックで PKCE に依存するため、`client_secret` は**含まれない**ことに 注意してください。 ```json { "client_id": "evm_client_live_…", "client_id_issued_at": 1718000000, "redirect_uris": ["https://yourapp.com/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none", "scope": "recipe:read gene:read", "client_name": "My MCP Connector" } ``` `token_endpoint_auth_method: "none"` は、そのクライアントがパブリックであることを 示します。トークン交換の認証はシークレットではなく PKCE で行われます。ここから先は 標準の[認可コード + PKCE フロー](./10-oauth2-pkce.md)を実行してください。 ## DCR とポータルの使い分け | | 動的登録 | 開発者ポータル | | --- | --- | --- | | クライアント種別 | パブリック(PKCE)のみ | パブリックまたはコンフィデンシャル | | スコープ | OIDC + 読み取り専用(`gene:read`、`recipe:read`、`reuse:query`) | 任意。書き込み/公開も可(セルフサービス)。審査制のスコープは申請制 | | レビュー | なし —— 即時 | セルフサービスのスコープはなし。`account:read`、`a2a`、`recipe:express` はスコープ単位でレビュー | | 向いている用途 | 実行時にプロビジョニングする MCP / エージェントのコネクタ | 公開を行う、またはシークレットが必要な名前付き連携 | エンドポイントのディスカバリドキュメント (`/.well-known/oauth-authorization-server`)は `registration_endpoint` を 公開しているため、RFC 7591 に対応したクライアントは自動的に見つけられます。 ## 関連 - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) —— 登録したクライアントが次に実行するフロー - [スコープ](./11-scopes.md) —— どのスコープがセルフサービスで、どれが申請制か - [アプリの登録](./20-registering-apps.md) —— 全機能を備えたアプリ向けのポータル経路 --- ## 14-secret-rotation # シークレットのローテーション コンフィデンシャルクライアントは `client_secret` でトークン交換を認証します。 定期的にローテーションし、漏洩が疑われる場合は直ちにローテーションしてください。 ローテーションでは**新しいシークレット**が発行され、それは一度だけ表示され、 イベントはアプリのローテーション履歴に記録されます。 > パブリック / PKCE 専用のクライアント(SPA、ネイティブアプリ、ほとんどの > エージェント、[動的登録](./13-dcr.md)されたクライアント)には、ローテーション > すべきシークレットが**ありません** —— それらを保護しているのは PKCE です。 > このページはコンフィデンシャルクライアントにのみ当てはまります。 ## シークレットをローテーションする [開発者ポータル](/dev/portal)でアプリを開き、**シークレットのローテーション**を 選択するか、エンドポイントを直接呼び出してください(セッション認証)。 ```bash curl -X POST https://evomap.ai/developer/clients/$CLIENT_ID/rotate-secret \ -b "evomap_sid=$SESSION" ``` レスポンスは新しいシークレットを**一度だけ**返します —— 後から再取得することは できません。 ```json { "client_secret": "evm_secret_…" } ``` 画面を離れる前にシークレットマネージャーに保管してください。失った場合は、 再度ローテーションして新しいものを発行してください。 ## ダウンタイムなしで切り替える 新しいシークレットはローテーション時に有効になるため、速やかに切り替わるよう デプロイの順序を組んでください。 1. **ローテーション**して新しいシークレットを取得します。 2. コードの交換やトークンの更新を行うすべてのサービスに**デプロイ**します —— シークレットストアを更新し、インスタンスを入れ替えてください。 3. 新しいシークレットでトークン交換が成功することを**検証**します。 ローテーションは認証情報の変更にあたるため、リクエストの最中ではなくデプロイの 時間枠で計画してください。すでに発行済みのアクセストークンは期限まで動作し続けます。 新しいシークレットが必要なのは、[`/oauth/token`](./10-oauth2-pkce.md)や その他のコンフィデンシャルクライアント向けエンドポイントへのバックチャネル呼び出しだけです。 ## ローテーション履歴 ポータルには、シークレットが最後にローテーションされた時期と回数が表示され、 ローテーションのタイムラインが一覧されます。履歴に記録されるのは**タイムスタンプ のみ**です —— シークレットそのものが保存・表示されることはありません。 ローテーションが予定どおり行われたかの監査や、予期しないローテーションの発見に 使ってください。 ## 推奨プラクティス - 定期的に(例えば四半期ごとに)ローテーションし、漏洩が疑われた場合は直ちに ローテーションしてください。 - シークレットをソース管理、ログ、クライアントサイドのバンドルに含めないでください —— コンフィデンシャルなシークレットはサーバー上にのみ置くべきものです。 - シークレットの機密性を保証できない場合(ブラウザアプリやモバイルアプリを 配布する場合など)は、コンフィデンシャルクライアントではなく PKCE を使った **パブリック**クライアントを使ってください —— そうすればローテーションすべき シークレットはそもそも存在しません。 ## 関連 - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) —— シークレットが使われる場所 - [アプリの登録](./20-registering-apps.md) —— アプリのライフサイクルと最初のシークレットの出どころ - [動的クライアント登録](./13-dcr.md) —— シークレットを持たないパブリッククライアント --- ## 20-registering-apps # アプリの登録 OAuth **アプリ**(クライアント)は、あなたの連携が EvoMap に対して自身を 識別するための仕組みです。アプリを登録すると `client_id` が —— コンフィデンシャル アプリの場合は一度だけ表示される `client_secret` も —— 得られ、 [OAuth 2.0 + PKCE](./10-oauth2-pkce.md)フローを実行できます。このページでは 作成、参照、更新、失効というライフサイクル全体を扱います。 アプリの管理は[開発者ポータル](/dev/portal)、または以下で示すセッション認証の `/developer/clients` API で行います。アプリの登録は**セルフサービス**です。 サインイン済みのアカウントなら、読み取り・下書き・公開のスコープを持つアプリを —— コンフィデンシャルでもパブリックでも —— 作成でき、その場で承認されます。 登録時に拒否されるのは審査制のスコープ(`account:read`、`a2a`、`recipe:express`) だけです。アプリ作成後にスコープ単位で申請するか、承認済みの開発者申請を持つか ([連携済みアプリ](./43-connected-apps.md)を参照)、それらすらセルフサービスな [テストモードのクライアント](./03-test-mode.md)を登録してください。 パブリックで読み取り専用のクライアントはセッションすら不要で、 [RFC 7591 で自己登録](./13-dcr.md)できます。 これらのエンドポイントは OAuth アクセストークンではなく、**ブラウザのセッション**で 認証します。サインインしてブラウザから `evomap_sid` cookie をコピーし、 `-b "evomap_sid=$SESSION"` の形で送ってください。アカウント全体を背負う個人の資格 情報なので、共有スクリプトや CI には置かず、単発の変更はポータルで行ってください。 `/developer/oauth/` 配下は逆で、Bearer アクセストークンだけを受け取り cookie は 見ません。 ## アプリを作成する アプリの名前、リダイレクト URI、申請するスコープを添えて `POST /developer/clients` を呼び出します。 ```bash curl -X POST https://evomap.ai/developer/clients \ -b "evomap_sid=$SESSION" \ -H "Content-Type: application/json" \ -d '{ "name": "Recipe Importer", "redirect_uris": ["https://yourapp.com/callback"], "allowed_scopes": ["recipe:read", "recipe:publish"], "description": "Imports recipes into the value pool", "homepage_url": "https://yourapp.com", "is_confidential": true }' ``` | フィールド | 必須 | 備考 | | --- | --- | --- | | `name` | ✅ | 同意画面に表示される表示名。 | | `redirect_uris` | ✅ | 完全一致のコールバック URL。認可呼び出しの `redirect_uri` はいずれかと一致する必要があります。 | | `allowed_scopes` | ✅ | アプリが申請できるスコープ。読み取り・下書き・公開のスコープはセルフサービスで、審査制のスコープはここでは拒否されます —— [スコープ](./11-scopes.md)を参照してください。 | | `description` | | 同意時にユーザーへ表示されます。 | | `homepage_url` | | アプリのホームページ。 | | `is_confidential` | | `true` にすると `client_secret` が発行されます(サーバーサイドのアプリ)。パブリックな PKCE クライアントでは省略するか `false` にします。 | | `test_mode` | | `true` にするとサンドボックスのクライアント(`evm_client_test_…`)として登録されます —— [テストモード](./03-test-mode.md)を参照してください。ポータルの作成フォームでは **テストモード(サンドボックス)** チェックボックスとして用意されています。 | レスポンスはクライアントを返し、コンフィデンシャルアプリの場合はシークレットを **ちょうど一度だけ**返します。 ```json { "client": { "clientId": "evm_client_live_…", "name": "Recipe Importer", "status": "approved", "isConfidential": true, "redirectUris": ["https://yourapp.com/callback"], "allowedScopes": ["recipe:read", "recipe:publish"] }, "client_secret": "evm_secret_…" } ``` 正確なステータスコードではなく `2xx` で分岐してください。`evomap.ai` では `POST /developer/clients` は **`200`** を返し、`/developer/oauth/` 配下の Bearer トークン API は `201` を返します。両者は別のレイヤーが処理しており、 ここで `201` を前提にしたクライアントは、ドキュメントが指すホストで失敗します。 `client_secret` は今すぐ保管してください —— 二度と表示されません(失った場合は ローテーションしてください。[シークレットのローテーション](./14-secret-rotation.md) を参照)。セルフサービスのスコープで登録したアプリは最初から `approved` です。 承認済みの開発者が審査制のスコープ付きで登録した場合だけ `pending` で始まり、 レビュー後に `approved` になります。 ## 自分のアプリを一覧・参照する ```bash # All your apps curl https://evomap.ai/developer/clients -b "evomap_sid=$SESSION" # One app curl https://evomap.ai/developer/clients/$CLIENT_ID -b "evomap_sid=$SESSION" ``` 各クライアントは `status`(`pending` · `approved` · `revoked`)、`redirectUris`、 `allowedScopes`、`clientSecretPrefix`、およびタイムスタンプを返します。 参照でシークレットの全体が返ることはありません —— 接頭辞のみが返るため、 どのシークレットが有効かを見分けられます。 ## アプリを更新する `PATCH /developer/clients/{clientId}` はリダイレクト URI、スコープ、メタデータを その場で編集します。変更するフィールドだけを送ってください。 ```bash curl -X PATCH https://evomap.ai/developer/clients/$CLIENT_ID \ -b "evomap_sid=$SESSION" \ -H "Content-Type: application/json" \ -d '{ "redirect_uris": ["https://yourapp.com/callback", "https://yourapp.com/callback2"] }' ``` その場での `PATCH` は小さな編集に向いた手軽な経路です。レビューを伴うアプリ全体の 設定変更をバージョン付きのスナップショットとして反映したい場合は、 [アプリのバージョニング](./21-app-versioning.md)を使ってください。 ## アプリを失効させる `POST /developer/clients/{clientId}/revoke` はアプリを無効にし、**そのトークンを 直ちに無効化します** —— そのアプリに発行されたすべてのアクセストークンと リフレッシュトークンが動作しなくなります。連携を終了するときや `client_id` が 侵害されたときに使ってください。 ```bash curl -X POST https://evomap.ai/developer/clients/$CLIENT_ID/revoke \ -b "evomap_sid=$SESSION" ``` ## 関連 - [テストモード](./03-test-mode.md) —— まずサンドボックスのクライアントで開発する - [シークレットのローテーション](./14-secret-rotation.md) —— コンフィデンシャルなシークレットを安全にローテーションする - [アプリのバージョニング](./21-app-versioning.md) —— レビューを伴うアプリ全体の設定変更 - [利用状況とアクティビティログ](./22-usage-logs.md) —— アプリの使われ方を監視する - [スコープ](./11-scopes.md) —— 各スコープが何を許可するか、追加申請の方法 --- ## 21-app-versioning # アプリのバージョニング 稼働中のクライアントをその場で編集する代わりに、アプリ全体の設定変更をレビュー可能な **バージョン**として反映します。名前、リダイレクト URI、スコープ、宣言した Webhook イベントを含む設定スナップショット全体を、変更履歴と正当性の説明とともに提出します。 モデレーターが承認するまで、稼働中のクライアントは現在の設定で動作し続けます。 承認されると、スナップショットはアトミックに適用されます。 - 更新した設定スナップショット、変更履歴、正当性の説明を添えて新しいバージョンを 提出します。 - バージョンがレビュー待ち(`pending`)の間、稼働中のアプリは現在の設定で動作し 続けます —— それを本番に昇格させるのは承認です。 - 1 つのアプリにつき、未処理(`draft` / `pending`)のバージョンは同時に最大 1 つです。 - スナップショットにはセルフサービスのスコープと審査制のスコープ(`account:read`、 `a2a`、`recipe:express`)を含められます。審査制のスコープを付与するのはレビュー 担当者で、スコープ単位のリクエストとまったく同じように承認時に行います。 `node:manage` のようなチーム承認のスコープはスナップショットから除外されます。 ## エンドポイント オーナー向けエンドポイントはセッション認証(開発者ポータル)です。レビュー用の エンドポイントにはモデレーター権限が必要です。 | メソッド | パス | 備考 | | --- | --- | --- | | POST | `/developer/clients/{clientId}/versions` | 新しいバージョンを提出 —— `{ config, changelog, justification }`。レート制限は 20/時 | | GET | `/developer/clients/{clientId}/versions` | アプリのバージョンを新しい順に一覧 | | GET | `/admin/oauth/client-versions` | モデレーター: レビューキュー · `?status=pending\|approved\|rejected\|all ?limit` | | PATCH | `/admin/oauth/client-versions/{id}` | モデレーター: `{ decision: approved\|rejected, reject_reason? }` —— 承認するとスナップショットが適用されます | リクエスト / レスポンスの完全な形式は、OpenAPI 仕様の **App versions** タグの下に あります: [OpenAPI 3.1 (JSON)](https://evomap.ai/openapi.json) · [YAML](https://evomap.ai/openapi.yaml)。 - その場で編集する経路は[アプリの登録](./20-registering-apps.md)、エンドポイントの すべては[API 概要](./40-api-overview.md)を参照してください。 --- ## 22-usage-logs # 利用状況とアクティビティログ アプリの使われ方を監視できます —— 集計された利用状況、注目すべきイベントの タイムライン、そして(ポータルでは)デバッグ用の直近の個別 API 呼び出しです。 3 つはいずれもオーナースコープで、ログイン中のセッションから読み取られます。 ## 利用状況のサマリー `GET /developer/clients/{clientId}/usage` は、アプリの集計スナップショットを返します —— どれだけ公開したか、何人のユーザーが認可したか、有効なトークン数、最後に アクティブだった時刻です。 ```bash curl https://evomap.ai/developer/clients/$CLIENT_ID/usage \ -b "evomap_sid=$SESSION" ``` ```json { "usage": { "publishedArtifacts": 42, "authorizedUsers": 128, "activeTokens": 96, "lastActiveAt": "2026-06-17T12:00:00Z" } } ``` `usage` オブジェクトは**オープンなマップ**です —— 上記のフィールドは代表例と みなし、追加のキーも許容してください。サマリーは時間とともに指標が増える 可能性があります。一目でわかる健全性の確認(アプリは稼働しているか、ユーザーは 何人か、有効なトークンはいくつか)に使い、リクエスト単位の集計には使わないでください。 ## アクティビティのタイムライン `GET /developer/clients/{clientId}/activity` は、アプリの注目すべきイベント —— 承認、設定変更、失効など —— のタイムラインを新しい順で返します。 ```bash curl https://evomap.ai/developer/clients/$CLIENT_ID/activity \ -b "evomap_sid=$SESSION" ``` ```json { "activity": [ { "type": "…", "at": "2026-06-17T12:00:00Z", "…": "event-specific fields" } ] } ``` 各エントリはオープンなオブジェクトです。必要なフィールドを読み取ってください。 「このアプリで何がいつ変わったか」に答えるためにアクティビティフィードを 使ってください。 ## 直近の API 呼び出し(オーナー向け診断) `GET /developer/clients/{clientId}/calls` は、アプリの直近の個別 API 呼び出しを、 メソッド、パス、HTTP ステータス、レイテンシとともに返します。これはセッション認証の オーナー向け診断です。アプリの OAuth アクセストークンではなく、EvoMap の セッションクッキーを使ってください。 ```bash curl "https://evomap.ai/developer/clients/$CLIENT_ID/calls?limit=50" \ -b "evomap_sid=$SESSION" ``` ```json { "calls": [ { "at": "2026-06-17T12:00:08Z", "method": "GET", "path": "/developer/oauth/recipes", "status": 200, "ms": 42 }, { "at": "2026-06-17T12:01:19Z", "method": "POST", "path": "/developer/oauth/recipes", "status": 503, "ms": 1200, "error": "service_temporarily_unavailable" } ] } ``` [開発者ポータル](/dev/portal)は**直近の呼び出し**ビューでも同じエンドポイントを 使っているため、連携をデバッグしながらエラーを見つけ、おおよそのエラー率を 算出できます。`limit` の既定値は 50 で、上限は 200 です。 ## 実際の使い方 - **ヘルスチェック** —— `usage` をポーリングして、アプリが稼働していることを確認し、 認可済みユーザー数と有効なトークン数を確認します。 - **監査** —— `activity` を読み、承認、編集、失効の履歴を確認します。 - **デバッグ** —— 連携の挙動がおかしいときは、ポータルの直近の呼び出しビューを開き、 HTTP ステータスから失敗した呼び出しを探します。 一覧が大きくなる場合のページネーションは、 [一貫性のプリミティブ](./42-consistency.md)にあるプラットフォーム共通の規約に従います。 ## 関連 - [アプリの登録](./20-registering-apps.md) —— これらのログが追跡するアプリのライフサイクル - [一貫性のプリミティブ](./42-consistency.md) —— ページネーションとレート制限の規約 - [Webhook](./30-webhooks.md) —— 利用状況をポーリングする代わりのプッシュ通知 --- ## 30-webhooks # Webhook Webhook エンドポイントを登録すると、API をポーリングする代わりに、イベントが 発生したとき —— レシピが作成、公開、または取り下げられたとき —— に **サーバープッシュ**の通知を受け取れます。EvoMap はイベントごとに署名済みの JSON エンベロープをあなたの HTTPS URL に POST し、失敗時にはリトライします。 Webhook はあなたの OAuth アプリのいずれかにスコープされます。クライアント単位で 登録し、そのアプリが関与するイベントで発火します。 ## エンドポイントを登録する HTTPS URL と受け取りたいイベント種別を添えて `POST /developer/clients/{clientId}/webhooks` を呼び出します。URL は登録時に **SSRF の観点で検証されます** —— `localhost`、プライベート/ループバックの IP 範囲、 クラウドメタデータのアドレスは拒否されるため、エンドポイントは実在する公開の HTTPS URL でなければなりません。 ```bash curl -X POST https://evomap.ai/developer/clients/$CLIENT_ID/webhooks \ -b "evomap_sid=$SESSION" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/hooks/evomap", "events": ["recipe.published", "recipe.takedown"] }' ``` 購読できるイベント種別は `recipe.created`、`recipe.published`、`recipe.takedown` です —— [イベントカタログ](./31-event-catalog.md)を参照してください。 ## 署名用シークレットは一度だけ表示されます `201` のレスポンスにはエンドポイントとその**署名用シークレット**が含まれます —— これが返るのは**作成時のみ**で、二度と表示されません。 ```json { "id": "wh_…", "url": "https://yourapp.com/hooks/evomap", "events": ["recipe.published", "recipe.takedown"], "secret": "whsec_…" } ``` `secret` は直ちにシークレットマネージャーに保管してください —— すべての配信を 検証するために必要です([Webhook のセキュリティ](./32-webhook-security.md)を参照)。 失った場合は、その Webhook を削除して新しく登録してください。 ## ping でエンドポイントを検証する 本番運用の前に、テスト配信を送ってください。`POST /developer/webhooks/{webhookId}/ping` は `ping` イベントを配信するので、 エンドポイントが POST を受け取れること、署名チェックがエンドツーエンドで 通ることを確認できます。 ```bash curl -X POST https://evomap.ai/developer/webhooks/$WEBHOOK_ID/ping \ -b "evomap_sid=$SESSION" ``` ## Webhook を管理する | メソッド | パス | 目的 | | --- | --- | --- | | POST | `/developer/clients/{clientId}/webhooks` | エンドポイントを登録(シークレットは一度だけ返る) | | GET | `/developer/clients/{clientId}/webhooks` | アプリの Webhook を一覧 | | DELETE | `/developer/webhooks/{webhookId}` | Webhook を削除 | | POST | `/developer/webhooks/{webhookId}/ping` | `ping` テストイベントを送信 | | GET | `/developer/webhooks/{webhookId}/deliveries` | 直近の配信試行を確認 | | POST | `/developer/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver` | 過去のイベントを再送 | Webhook の管理はセッション認証(開発者ポータル / ログイン中のセッション)で、 オーナースコープです —— 自分のアプリの Webhook のみ管理できます。 ## 実装すべきこと 1. JSON ボディの `POST` を受け付ける公開 HTTPS エンドポイントを用意します。 2. 信頼する前に、すべてのリクエストで**署名を検証**してください —— [Webhook のセキュリティ](./32-webhook-security.md)。 3. **速やかに `2xx` を返し**(数秒以内)、時間のかかる処理は非同期で行ってください —— 遅いレスポンスや 2xx 以外のレスポンスは配信失敗として扱われ、 [リトライされます](./33-webhook-delivery.md)。 4. **`event.id` で重複排除してください** —— 再配信は同じ `evt_…` の id を 繰り返します。 ## 関連 - [イベントカタログ](./31-event-catalog.md) —— イベント種別とペイロード - [Webhook のセキュリティ](./32-webhook-security.md) —— 署名の検証とリプレイの防止 - [配信とリトライ](./33-webhook-delivery.md) —— リトライのスケジュールと再配信 --- ## 31-event-catalog # イベントカタログ すべての Webhook 配信は、イベント種別にかかわらず同じトップレベルの形をもつ 署名済みの JSON エンベロープです。[Webhook を登録する](./30-webhooks.md)ときに 関心のある種別を購読してください。EvoMap は一致するイベントごとにエンベロープを POST します。 ## エンベロープ ```json { "id": "evt_…", "type": "recipe.published", "created": "2026-06-17T12:00:00Z", "livemode": true, "data": { "…": "event-specific fields" } } ``` | フィールド | 型 | 備考 | | --- | --- | --- | | `id` | string | 一意なイベント id(`evt_…`)。**これで重複排除してください** —— 再配信では同じ値が繰り返されます。 | | `type` | string | イベント種別(下の表を参照)。 | | `created` | string | イベントが発生した時刻の ISO-8601 タイムスタンプ。 | | `livemode` | boolean | 実イベントでは `true`、テストモードのクライアントが生成したイベントでは `false`。 | | `data` | object | イベント固有のペイロード —— 対象となったリソース。 | `livemode` があるため、1 つのエンドポイントで実トラフィックと [テストモード](./03-test-mode.md)のトラフィックを安全に扱えます。これで分岐させ、 サンドボックスのイベントが本番の状態に触れないようにしてください。 ## イベント種別 | 種別 | 購読可否 | 発火するタイミング | | --- | --- | --- | | `recipe.created` | ✅ | レシピの**下書き**が作成されたとき。 | | `recipe.published` | ✅ | レシピが公開バリュープールに入ったとき。 | | `recipe.takedown` | ✅ | 公開済みのレシピが削除されたとき。 | | `ping` | — | エンドポイントを検証するために自分でトリガーする[テスト配信](./30-webhooks.md)。購読可能な種別ではありません。 | 登録時の `events` 配列には、購読可能な種別(`recipe.created`、`recipe.published`、 `recipe.takedown`)から選んで指定します。`ping` は ping エンドポイントを明示的に 呼び出したときにのみ配信されるため、購読することはありません —— それでも ハンドラーは受け付けられるようにしておくべきです(実イベントとまったく同様に 署名付きで届きます)。 ## `data` ペイロード `data` はイベントの対象となるリソースを運びます —— `recipe.*` 系の種別では、 対象となったレシピです。`data` は**オープンなオブジェクト**として扱ってください。 必要なフィールドを読み取り、追加のフィールドも許容してください。ペイロードは 破壊的変更なしに時間とともにフィールドが増える可能性があります。判断に迷う場合は、 特定の `data` フィールドが存在することに依存するのではなく、エンベロープの `id` / `type` を使って[API](./40-api-overview.md)経由でリソースを引き当ててください。 ## 扱い方の指針 - **`event.id` で重複排除**してください —— リトライや手動の再配信は同じ id を 再利用します。 - **`livemode` で分岐**させ、テストイベントが本番データを変更しないようにしてください。 - **順序を仮定しないでください** —— 配信は順序が入れ替わって届いたり、リトライされたり します。ハンドラーはべき等になるよう設計してください。 ## 関連 - [Webhook](./30-webhooks.md) —— エンドポイントの登録とイベントの購読 - [Webhook のセキュリティ](./32-webhook-security.md) —— 各配信が正当なものか検証する - [配信とリトライ](./33-webhook-delivery.md) —— エンドポイントが失敗したときに起きること --- ## 32-webhook-security # Webhook のセキュリティ 公開 URL には誰でも POST できるため、それに基づいて処理を行う前に**すべての配信を 検証してください**。EvoMap は、[エンドポイントを登録した](./30-webhooks.md)ときに 受け取った署名用の `secret` を鍵として、各 Webhook に HMAC で署名します。 検証に失敗したリクエストは拒否しなければなりません。 ## 署名ヘッダー 各配信には次のヘッダーが付きます。 ``` X-EvoMap-Webhook-Signature: t=1718000000,v1= ``` - `t` —— 署名が作成された Unix タイムスタンプ。 - `v1` —— HMAC-SHA256 を 16 進数でエンコードしたもの。あなたの Webhook `secret` を 鍵として、文字列 `` `${t}.${rawBody}` ``(タイムスタンプ、リテラルの `.`、 そして**生のリクエストボディ**)に対して計算します。 後方互換性のため、旧来の `X-EvoMap-Signature: sha256=` ヘッダー (ボディのみに対する HMAC で、タイムスタンプを含まない)も送信されます。 `X-EvoMap-Webhook-Signature` を優先してください —— リプレイを拒否できるのは タイムスタンプ付きの方式です。 ## 配信を検証する `` `${t}.${rawBody}` `` に対して期待される `v1` を計算し、ヘッダーの値と **一定時間で**比較してください。重要なルールが 2 つあります。 1. 受け取ったそのままの**生のボディバイト**に対して署名を検証してください —— 再シリアライズした JSON オブジェクトに対して検証すると、キーの順序や空白が 異なるため必ず失敗します。 2. `t` が許容範囲(例えば ±5 分)を外れている配信は、キャプチャされたものの リプレイに備えて拒否してください。 ```javascript import { createHmac, timingSafeEqual } from "node:crypto"; /** * @param {string} rawBody - the exact request body bytes * @param {string} header - value of X-EvoMap-Webhook-Signature * @param {string} secret - your webhook signing secret (whsec_…) * @param {number} toleranceSec * @returns {boolean} */ export function verifyWebhook(rawBody, header, secret, toleranceSec = 300) { const parts = Object.fromEntries( header.split(",").map((kv) => kv.split("=")), ); const t = Number(parts.t); if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const a = Buffer.from(expected); const b = Buffer.from(parts.v1 || ""); return a.length === b.length && timingSafeEqual(a, b); } ``` ```python import hmac, hashlib, time def verify_webhook(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool: parts = dict(kv.split("=", 1) for kv in header.split(",")) t = int(parts.get("t", 0)) if not t or abs(time.time() - t) > tolerance: return False signed = f"{t}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts.get("v1", "")) ``` ## チェックリスト - **まず生のボディを読み取ってください。** JSON のパースやフレームワークの ミドルウェアが再シリアライズする前に、ボディのバイト列を取得してください。 - **一定時間での比較**(`timingSafeEqual` / `hmac.compare_digest`)を使い、 `==` は絶対に使わないでください —— タイミングのサイドチャネルを避けるためです。 - **タイムスタンプの許容範囲を強制してください。** 署名が有効でも `t` が古いものは リプレイです。拒否してください。 - **検証が済んでから `2xx` を返してください。** 検証に失敗した場合は `4xx` を返し、 何もしないでください。 - **シークレットはサーバー側に保持してください。** 漏洩の可能性がある場合は ローテーション(Webhook の削除と再登録)してください。 ## 関連 - [Webhook](./30-webhooks.md) —— 登録と一度だけ表示される署名用シークレット - [イベントカタログ](./31-event-catalog.md) —— 検証対象のエンベロープ - [配信とリトライ](./33-webhook-delivery.md) —— 拒否した配信が引き起こすこと --- ## 33-webhook-delivery # 配信とリトライ Webhook の配信試行はすべて記録されるため、失敗の調査やイベントの再送が できます。エンドポイントが短時間ダウンしていた場合は EvoMap が自動で リトライします。より長くダウンしていた場合は、復旧後に手動で再配信できます。 ## 配信レコード `GET /developer/webhooks/{webhookId}/deliveries` は直近の試行を一覧します (オーナーのみ)。各レコードは約 **7 日間** 保持されます。 ```json { "id": "whd_…", "event": "recipe.published", "event_id": "evt_…", "status": "failed", "http_status": 500, "attempts": 3, "last_error": "endpoint returned 500", "created_at": "2026-06-17T12:00:00Z", "delivered_at": null } ``` | フィールド | 意味 | | --- | --- | | `id` | 配信 id(`whd_…`)— 再配信エンドポイントに渡します。 | | `event` / `event_id` | イベント種別と、その `evt_…` id。 | | `status` | `delivered` または `failed`。 | | `http_status` | エンドポイントが返した HTTP ステータス(到達できなかった場合は `null`)。 | | `attempts` | 配信を試行した回数。 | | `last_error` | 直近の失敗理由(配信が成功すると `null`)。 | | `created_at` / `delivered_at` | イベントがキューに入った / 正常に配信された時刻。 | 配信が成功と見なされるのは、エンドポイントが **`2xx`** を返したときだけです。 2xx 以外のレスポンス、タイムアウト、接続失敗はいずれも試行を失敗として記録し、 リトライをスケジュールします。 ## 自動リトライ 失敗した配信は **指数バックオフ** で自動的にリトライされます。リトライごとに 待ち時間が前回より長くなるため、短時間の障害は何もしなくても自然に復旧します。 リトライは配信が成功するか、試行回数を使い切った時点で止まります。最終状態は 配信レコードで確認できます。 リトライ(および手動の再配信)は **同じ `event.id`** を繰り返すため、ハンドラーは べき等でなければなりません。その id で重複排除して、再配信されたイベントを 二重に処理しないようにしてください。[イベントカタログ](./31-event-catalog.md) を参照してください。 ## 手動での再配信 エンドポイントを修正したら、`POST /developer/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver` で過去の特定の イベントを再送できます(オーナーのみ)。 ```bash curl -X POST \ https://evomap.ai/developer/webhooks/$WEBHOOK_ID/deliveries/$DELIVERY_ID/redeliver \ -b "evomap_sid=$SESSION" ``` これは元々記録されたイベントをもう一度配信します。`event.id` は同じなので、 重複排除ロジックがあれば安全に再実行できます。 ## 確実な配信のためのエンドポイント設計 - **`2xx` を素早く返す。** (署名を検証したうえで)受領を確認し、処理をキューに 入れて非同期で実行してください。リクエストを長く占有する遅いハンドラーは 失敗と見なされ、リトライされます。 - **べき等にする。** `event.id` で重複排除し、どのイベントも複数回届きうると 想定してください。 - **順序に依存しない。** リトライとバックオフがあるため、イベントは順不同で 届きえます。 - **配信一覧を監視する** — 展開中はエンドポイントが `2xx` を返していることを 確認してください。 ## 関連 - [Webhook](./30-webhooks.md) — 登録、ping、管理 - [イベントカタログ](./31-event-catalog.md) — エンベロープと重複排除用の `event.id` - [Webhook セキュリティ](./32-webhook-security.md) — `2xx` を返す前に検証する --- ## 40-api-overview # API 概要 API を呼び出すときは、アクセストークンを Bearer 認証情報として渡します。 レスポンスはすべて JSON です。下のエンドポイント表は OpenAPI 仕様から リアルタイムに描画されます。本記事の下にあるインタラクティブなコンポーネントが `/openapi.json` を直接読むため、デプロイ済みの実際の API 表面とずれることは ありません。 機械可読な仕様: [OpenAPI 3.1 (JSON)](https://evomap.ai/openapi.json) · [YAML](https://evomap.ai/openapi.yaml) — Postman / Insomnia にインポートしたり、 型付きクライアントを生成したりできます。 ## スコープで保護されたデータエンドポイント | メソッド | パス | スコープ | 備考 | | --- | --- | --- | --- | | GET | `/developer/oauth/recipes` | `recipe:read` | プロモート済みレシピカタログ · `?q ?limit` | | GET | `/developer/oauth/genes` | `gene:read` | ランキング付きの公開アセットカタログ · `?type ?limit` | | GET | `/developer/oauth/reuse` | `reuse:query` | 再利用 / 関連グラフ · `?asset_id \| ?recipe_id` | | POST | `/developer/oauth/recipe` | `recipe:write` | レシピの下書きを作成する | | POST | `/developer/oauth/recipe/publish` | `recipe:publish` | レシピを作成して公開する | ## OAuth データ API が扱わない範囲 ジーンとカプセル —— ランキング付きの公開**アセット** —— はここでは読み取り専用です。 `gene:read` が解放するのは `GET /developer/oauth/genes` だけで、OAuth トークンで アセットカタログに書き込むエンドポイントはなく、`gene:write` のようなスコープも 存在しません。アセットは **エージェントノード** が A2A プロトコル経由で公開します。 `POST /a2a/hello` でノードを登録し、そのノードの `node_secret` で認証して、Gene + Capsule のバンドルを `POST /a2a/publish` に送ります。 [エージェントのオンボーディングページ](/onboarding/agent)にそのまま使える リクエスト例があり、`GET /a2a/skill?topic=publish` がエンベロープの仕様を説明して います。OAuth アプリが書き込める唯一のアセット種別はレシピです (`recipe:write` / `recipe:publish`)。 `POST /a2a/hello` について、その `?topic=hello` のリファレンス自体が現在は誤って いる点が 2 つあります。レスポンスは GEP-A2A の**エンベロープ**で、`your_node_id` と `node_secret` はトップレベルではなく `payload` の下にあります(`?topic=publish` の ページは正しい記述です)。また、**拒否も HTTP `200`** で返り、理由は `payload.status: "rejected"` に入ります。ステータスコードしか見ないクライアントは これを成功と解釈し、空のシークレットのままループします。まず `payload.status` を 確認してください。 ## OAuth 2.0 プロトコルエンドポイント | メソッド | パス | 備考 | | --- | --- | --- | | GET | `/oauth/authorize` | 同意フローを開始する(PKCE S256) | | POST | `/oauth/token` | 認可コード / リフレッシュトークンをトークンと交換する | | POST | `/oauth/revoke` | トークンを失効させる(RFC 7009) | | POST | `/oauth/introspect` | トークンイントロスペクション(RFC 7662) | | GET | `/.well-known/oauth-authorization-server` | エンドポイントディスカバリー(RFC 8414) | ## マーケットプレイスカタログとユーザーインストール 公開カタログは認証不要です。`/marketplace/me/*` ビューはセッション認証が 必要です。ユーザーの「インストール」は `/oauth/authorize` が記録した OAuth 同意そのものであり、サーバー側のインストールショートカットは存在しません。 | メソッド | パス | 認証 | 備考 | | --- | --- | --- | --- | | GET | `/marketplace/apps` | 公開 | 公開済みアプリ · `?category ?q ?limit ?cursor` | | GET | `/marketplace/apps/{slug}` | 公開 | slug で 1 件の公開済みアプリを取得 | | GET | `/marketplace/apps/{slug}/install-state` | 公開 | 呼び出し側のインストール可否(未ログインでも可読) | | GET | `/marketplace/me/installations` | セッション | 自分がインストールしたユーザー向けアプリ | | DELETE | `/marketplace/me/installations/{clientId}` | セッション | アンインストール = OAuth 同意の取り消し。evomap.ai では提供されないため `POST /oauth/consents/{clientId}/revoke` を使ってください | ## アプリ掲載とダッシュボード(オーナー) アプリオーナー向けのセッション認証ポータルエンドポイントです。 | メソッド | パス | 備考 | | --- | --- | --- | | GET | `/developer/clients/{clientId}/listing` | マーケットプレイス掲載情報を読む | | PUT | `/developer/clients/{clientId}/listing` | 掲載ドラフトを作成 / 更新する | | POST | `/developer/clients/{clientId}/listing/submit` | モデレーター審査に提出する | | DELETE | `/developer/clients/{clientId}/listing` | 掲載を非表示 / アーカイブする | | GET | `/developer/clients/{clientId}/dashboard` | 集約ダッシュボード:設定・掲載・審査状況・インストール数 | ## テナントアプリインストール(組織管理者) セッション認証の組織管理エンドポイントです(インストール申請の作成は メンバー権限で可能)。API エクスプローラーでは参照のみで、Bearer トークン では呼び出せません。インストールは付与スコープ + アプリバージョンを同意 スナップショットとして凍結し、アプリ側の変化は権限を黙って広げる代わりに `reauth_required` を立てます。 | メソッド | パス | ロール | 備考 | | --- | --- | --- | --- | | GET | `/org/{orgId}/apps` | 管理者 | インストール一覧 · `?status` | | POST | `/org/{orgId}/apps` | 管理者 | ボディの `client_id` でインストール | | POST | `/org/{orgId}/apps/{installationId}/disable` | 管理者 | 発行済みトークンを失効、グラントは維持 | | POST | `/org/{orgId}/apps/{installationId}/enable` | 管理者 | トークン発行を再開 | | POST | `/org/{orgId}/apps/{installationId}/revoke` | 管理者 | トークンを失効しグラントも取り消す | | GET | `/org/{orgId}/app-install-requests` | 管理者 | メンバー申請の受信箱 · `?status` | | POST | `/org/{orgId}/app-install-requests` | メンバー | アプリインストールを申請する | | POST | `/org/{orgId}/app-install-requests/{requestId}/approve` | 管理者 | 承認して実インストール化 | | POST | `/org/{orgId}/app-install-requests/{requestId}/reject` | 管理者 | 却下(メモを添付可能) | | GET | `/org/{orgId}/marketplace/installations` | 管理者 | 同じ一覧の marketplace プレフィックス版 | | POST | `/org/{orgId}/marketplace/apps/{clientId}/install` | 管理者 | パスの `clientId` でインストール | | GET | `/org/{orgId}/marketplace/installations/{installationId}` | 管理者 | ドリフト内訳つき詳細 | | POST | `/org/{orgId}/marketplace/installations/{installationId}/reauthorize` | 管理者 | 同意スナップショットを更新 | | DELETE | `/org/{orgId}/marketplace/installations/{installationId}` | 管理者 | アンインストールし組織グラントを取り消す | ## エラー エラーは、フラットな JSON ボディ内の安定した機械可読コードで表されます。 OAuth プロトコルエンドポイントは RFC 6749 形式の `error` 値に従います。 開発者データ API のエラーは `type` と `request_id` も含む場合があります。 レート制限と公開クォータには、機械が直接処理できるリトライ時刻情報が付きます。 コード表の全体とトラブルシューティング手順は [エラーコード](./44-error-codes.md)、 統一エラーボディ・ページネーション・べき等性・レート制限ヘッダーについては [一貫性プリミティブ](./42-consistency.md) を参照してください。 ## その場で試す [API エクスプローラー](./41-api-explorer.md) を使えば、Bearer トークンで呼べる エンドポイントをブラウザーから直接呼び出せます。 --- ## 41-api-explorer # API エクスプローラー Bearer トークンで呼べるエンドポイントを、`curl` もドキュメントを離れることも なくブラウザーから試せます。インタラクティブなコンソールは **本記事の下** に 表示されます。アクセストークンを貼り付け、エンドポイントを選び、パラメーターを 入力して送信してください。 ## 仕組み - **稼働中の OpenAPI 仕様**(`/openapi.json`)を取得し、**すべての** エンドポイントを一覧します。[API 概要](./40-api-overview.md) で説明している データエンドポイントと公開エンドポイントと同じもので、常にデプロイ済みの内容と 同期しています。 - リクエストは EvoMap に対して **同一オリジン** で発行されます。アクセストークンは ブラウザー内にとどまり、実際に行う呼び出しの際に EvoMap にのみ送信されます。 サードパーティのプロキシは経由しません。 - レスポンス(ステータス、選択したヘッダー、JSON ボディ)はインラインで表示され、 `pagination`、`livemode`、`request_id`、リトライ関連ヘッダーを含む正確な形を 確認できます。 ## 実行できるもの、参照専用にとどまるもの 判定するルールは 2 つで、どちらが適用されるかはコンソールが示します。 - **実行可能 — Bearer トークンで呼べるすべてのエンドポイント。** `/developer/oauth/*` のデータ・公開エンドポイントや `GET /oauth/userinfo` を含む、すべての `oauth2` オペレーション。貼り付けたアクセストークンが、まさにこれらが必要と する認証情報です。 - **実行可能 — 公開ディスカバリードキュメント。** `GET /.well-known/oauth-authorization-server`、`GET /.well-known/openid-configuration`、`GET /.well-known/jwks.json` は 読み取り専用の静的 JSON で、認証情報はまったく不要です。 - **参照専用 — `POST /oauth/token`、`/oauth/register`、`/oauth/introspect`、 `/oauth/revoke`。** これらは `client_secret` を受け取るか発行するもので、 `revoke` は有効なトークンを破棄します。クライアントシークレットを貼り付けたり、 いま検証に使っているトークンを消し飛ばしたりする場所としてドキュメントページは 適切ではないため、意図的に実行不可にしています。代わりに自分のアプリから [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) のフローを使ってください。 - **参照専用 — ポータルおよび管理系エンドポイント。** `/developer/clients/*`、 `/developer/webhooks/*`、`/oauth/authorize` 以下のものとその他は、Bearer トークンではなく **ポータルのセッション Cookie** で認証します。それらには [開発者ポータル](/dev/portal) を使ってください。 参照専用のエンドポイントを選んだ場合も、メソッド・パス・概要は表示されます。 加えて、ここから送信できない理由が 1 行で示されます。 ## コードスニペットとサーバーセレクター 作成したリクエストは、4 つの言語のコピー可能なコードスニペットとしても 描画されます。**curl**、**JavaScript (`fetch`)**、**Python (`requests`)**、**Go (`net/http`)** です。コードスニペットは認証情報を環境から読み取ります (`$ACCESS_TOKEN`、`process.env.ACCESS_TOKEN`、`os.environ["ACCESS_TOKEN"]`、 `os.Getenv("ACCESS_TOKEN")`)。貼り付けたトークンが埋め込まれることはないため、 コードスニペットはバグ報告にそのまま貼り付けても安全です。**サーバーセレクター** (本番 `https://evomap.ai` またはステージング `https://dev.evomap.ai`)は、生成される コードスニペット内のベース URL だけを変えます。ブラウザー内の試行呼び出しは常に 同一オリジンのままなので、トークンが別のホストに送られることはありません。 ## トークンを取得する 何かを呼び出すにはアクセストークンが必要です。 1. アプリで [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) のフローを実行して `access_token` を取得するか、アプリがすでに保持しているものを用意します。 2. コンソールのトークン欄に貼り付けます。 3. トークンの [スコープ](./11-scopes.md) によって、どのエンドポイントが成功するかが 決まります。トークンに無いスコープを必要とする呼び出しは `403 insufficient_scope` を返します。 ## テストトークンを使う 試行中は **[テストモード](./03-test-mode.md)** のトークンを使うことを推奨します。 公開は隔離されたサンドボックスで実行され(実際のバリュープールには一切影響しません)、 レスポンスには `livemode: false` が付きます。本番の挙動を検証するときにだけ、 ライブトークンに切り替えてください。 ## 関連 - [API 概要](./40-api-overview.md) — エンドポイント表の全体(こちらも仕様からリアルタイムに描画されます) - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — アクセストークンの取得方法 - [一貫性プリミティブ](./42-consistency.md) — レスポンスで目にするページネーション、ヘッダー、エラーボディ - [エラーコード](./44-error-codes.md) — 安定したエラーコード、リトライの指針、トラブルシューティング手順 --- ## 42-consistency # 一貫性プリミティブ API 全体に共通して適用される横断的な規約です。ページネーション、べき等性、 レート制限、そして統一されたエラーボディ。一度覚えれば、[API 概要](./40-api-overview.md) に載っているすべてのエンドポイントで通用します。 ## ページネーション データ API のリストレスポンスには必ず `pagination` オブジェクトが付きます。 ```json { "recipes": [ … ], "pagination": { "limit": 20, "next_cursor": "…", "has_more": true } } ``` | フィールド | 意味 | | --- | --- | | `limit` | 実際に適用されたページサイズ(`?limit`、1–100、デフォルト 20)。常に存在します。 | | `next_cursor` | 不透明なキーセットカーソル — 次のページを取るには `?cursor` として渡し返します。最後のページでは `null`。 | | `has_more` | さらに次のページが存在するかどうか。 | キーセットカーソル方式のカタログ(例: レシピカタログ)は 3 つのフィールドすべてを 返します。上限のある top-N フィード(ランキング付きジーン、再利用の近傍、 関連度順のテキスト検索)は 1 ページだけを返し、**`limit` のみ** を持ちます (`next_cursor` / `has_more` は存在しません)。ページネーションはオフセットを インクリメントするのではなく、`next_cursor` で駆動してください。 ## べき等性 レシピの作成では、リトライを安全にするためのオプションの **`Idempotency-Key`** ヘッダー(8–255 文字)を受け付けます。 ```bash curl -X POST https://evomap.ai/developer/oauth/recipe \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Idempotency-Key: 3f9a…-a-stable-key" \ -H "Content-Type: application/json" \ -d '{ "title": "…" }' ``` - **同じキー** による内容の同一なリトライは、2 つ目のレシピを作成する代わりに 元の `201` を再生します。 - 同じキーを **異なるボディ** で使い回すと `422` が返ります。キーは最初の リクエストの内容に紐づいています。 論理的な操作ごとに 1 つキーを生成し(例: UUID)、リトライ時にそれを再利用してください。 ## レート制限 データ API の読み取りはアクセストークン単位でレート制限されます。すべての レスポンスが現在のウィンドウの状況を公開します。 | ヘッダー | 意味 | | --- | --- | | `X-RateLimit-Limit` | 1 ウィンドウあたりに許可されるリクエスト数。 | | `X-RateLimit-Remaining` | 現在のウィンドウで残っているリクエスト数。 | | `X-RateLimit-Reset` | ウィンドウがリセットされる Unix 秒。 | 上限を超えると `Retry-After` ヘッダー付きの `429` が返り、**さらに** 機械が直接 処理できるタイミング情報を含む JSON ボディが付きます。 ```json { "error": "rate_limited", "retry_after_ms": 1200, "next_request_at": "2026-06-17T12:00:01Z", "bucket": "…", "hint": "…", "agent_instruction": "…" } ``` すぐにリトライするのではなく、`next_request_at`(または `retry_after_ms`)まで バックオフしてください。`agent_instruction` は自律エージェントが扱いやすい 平易な言葉の指示です。 > **公開クォータは別枠です。** **公開** エンドポイントからの `429` はレート制限では > なく *クォータ* のレスポンスです。ボディがクォータの階層を説明し、(ソフト降格の > 場合は)クォータがいつリセットされるかを示す `X-Quota-Restored-At` ヘッダーを > 持ちます。[API 概要](./40-api-overview.md) を参照してください。 ## エラーボディ すべての `4xx`/`5xx` はフラットな `error` エンベロープを返します。最も完全な形は次のとおりです。 ```json { "error": "insufficient_scope", "error_description": "…", "request_id": "req_…", "type": "auth_error" } ``` | フィールド | 意味 | | --- | --- | | `error` | 機械可読なコード。OAuth プロトコルエンドポイントはここに RFC 6749 のコードを使います。 | | `error_description` | 任意の、人間が読める詳細。 | | `request_id` | 相関 id。`X-Request-Id` レスポンスヘッダーと一致します — **サポートへの問い合わせでは必ず添えてください**。 | | `type` | 粗い分類: `auth_error` · `invalid_request` · `rate_limited` · `conflict` · `not_found` · `server_error` · `service_unavailable`。 | 個別の処理は `error` で分岐し、粗い分類(例: 「リトライ可能かどうか」)は `type` で 分岐してください。`request_id` は必ずログに残してください。サポートが呼び出しを 追跡する手段はこれです。 `type` と `request_id` が付くのは開発者データ API のエラーだけです。OAuth プロトコルの エンドポイントは RFC 6749 形式(`error` と任意の `error_description`)で応答し、 スキーマ検証の失敗は `details` 配列付きの `validation_error` を、セッション Cookie の API と `GET /a2a/assets` は `unauthorized` を返します。いずれも `type` と `request_id` を 持ちません。[エラーコード](./44-error-codes.md)を参照してください。 ## 関連 - [API 概要](./40-api-overview.md) — エンドポイントの範囲と OpenAPI リンク - [API エクスプローラー](./41-api-explorer.md) — これらのヘッダーとボディを実際に確認する - [エラーコード](./44-error-codes.md) — 安定したコード、リトライの指針、トラブルシューティング手順 - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — 認証エラー(`401`/`403`)を文脈の中で理解する --- ## 44-error-codes # エラーコード すべての API エラーには安定した `error` コードがあります。厳密なハンドリングには `error` で分岐し、粗い分類には `type` で分岐してください。また `request_id` が 存在する場合は必ずログに残してください。サポートが呼び出しを追跡できます。 ## エラーエンベロープ OAuth プロトコルエンドポイントは RFC 6749 形式のエラーに従います。 ```json { "error": "invalid_request", "error_description": "code_challenge_method is required and must be S256" } ``` 開発者データ API のエラーは同じフラットな `error` フィールドを保ちつつ、粗い `type` と追跡可能な `request_id` を追加する場合があります。 ```json { "error": "insufficient_scope", "scope": "recipe:publish", "type": "auth_error", "request_id": "req_..." } ``` スキーマ検証はすべてのハンドラーより先に実行されます。ボディやフォームが OpenAPI スキーマに合わないと、`validation_error` と、問題のあるフィールドを列挙した `details` 配列が返ります。このエンベロープには `type`、`error_description`、 `request_id` が**なく**、ハンドラーが返すはずだったコードも現れません。 `POST /oauth/token` に未知の `grant_type` を送ると `unsupported_grant_type` ではなく `validation_error` に、`redirect_uris` のない `POST /oauth/register` は `invalid_request` ではなく `validation_error` になります。 ```json { "error": "validation_error", "details": [ { "path": ["grant_type"], "message": "Invalid option: expected one of \"authorization_code\"|\"refresh_token\"|..." } ] } ``` レート制限のレスポンスには、機械が直接処理できるリトライのタイミング情報が 含まれます。 ```json { "error": "rate_limited", "retry_after_ms": 1200, "next_request_at": "2026-06-17T12:00:01Z", "hint": "Rate limited. Wait until next_request_at before retrying, then add a small jitter (50-300ms).", "agent_instruction": "sleep_until_next_request_at" } ``` ## エラータイプ | タイプ | HTTP | 意味 | リトライ | 対処 | | --- | --- | --- | --- | --- | | `auth_error` | 401 / 403 | 認証情報が欠落、無効、期限切れ、またはスコープが不足しています。 | いいえ | トークンをリフレッシュし、足りないスコープを申請するか、ユーザーに再度同意を通してください。 | | `invalid_request` | 400 / 422 | パラメーター、JSON ボディ、PKCE フィールド、またはべき等性の使い方が不正です。 | いいえ | OpenAPI スキーマに照らしてリクエストを検証し、`error_description` が指摘したフィールドを修正してください。 | | `rate_limited` | 429 | トークン、組織、IP、または公開クォータのいずれかのウィンドウを超過しました。 | はい | `next_request_at`、`Retry-After`、または `X-Quota-Restored-At` まで待ってください。リトライ前にジッターを加えます。 | | `conflict` | 409 | リクエストが現在の状態と競合しています。 | 場合により | 処理中のべき等キーのように一時的なコードのときだけリトライしてください。それ以外はまず状態を解消します。 | | `not_found` | 404 | リソースが存在しない、または呼び出し元の所有物ではありません。 | いいえ | id、所有関係、テスト / ライブモードを確認してください。 | | `service_unavailable` | 503 | 一時的なインフラまたはキャパシティの問題です。 | はい | 指数バックオフを使い、サポート用に `request_id` を保持してください。 | | `server_error` | 500 | 想定外のサーバー障害です。 | はい | バックオフしてリトライしてください。続く場合は `request_id` を添えてサポートに連絡します。 | ## 主なコード 「タイプ」列はレスポンスが実際に持つ `type` フィールドです。OAuth プロトコルのボディ (`OAuthProtocolError`)とハンドラー前のボディ(`validation_error`、`unauthorized`)には 存在しないため、該当行はダッシュで示しています。 | コード | HTTP | タイプ | 発生箇所 | リトライ | 対処 | | --- | --- | --- | --- | --- | --- | | `invalid_request` | 400 | —(なし) | OAuth プロトコルのエンドポイント(authorize、token、register)、アプリ登録 | いいえ | `error_description` が指すパラメーターの欠落や不正を修正してください。ボディは `error` と `error_description` だけです。 | | `invalid_request` | 400 | `invalid_request` | Bearer データ API | いいえ | 欠落または不正なパラメーター / ボディフィールドを修正してください。 | | `validation_error` | 400 | —(なし) | あらゆる JSON / フォームボディ: OAuth トークン、DCR、アプリ登録、公開 | いいえ | `details` に列挙されたフィールドをすべて修正してください。このエンベロープに `type` と `request_id` はなく、エンドポイント固有のコード(`unsupported_grant_type` など)はボディが検証を通ってから初めて現れます。 | | `invalid_idempotency_key` | 400 | `invalid_request` | 公開 | いいえ | 8〜255 文字の `Idempotency-Key` を送ってください。 | | `invalid_client` | 401 | —(なし) | OAuth トークン交換 | いいえ | `client_id`、クライアントシークレット、クライアントが有効かどうかを確認してください。 | | `invalid_grant` | 400 | —(なし) | OAuth トークン交換 | いいえ | 認可コードまたはリフレッシュトークンが不明・期限切れ・失効済み、あるいは 2 分の再試行ウィンドウを過ぎています。ウィンドウ*内*の再送は、このエラーではなく `200` と同一のトークンを返します。[OAuth 2.0 + PKCE](./10-oauth2-pkce.md) を参照してください。 | | `unsupported_grant_type` | 400 | —(なし) | OAuth トークン交換 | いいえ | ディスカバリードキュメントに載っているサポート対象のグラントタイプを使ってください。 | | `invalid_scope` | 400 | —(なし) | OAuth の同意 / トークンリクエスト | いいえ | そのクライアントに登録済みのスコープだけを要求してください。 | | `login_required` | 401 | —(なし) | OAuth authorize | いいえ | 同意を開始する前にユーザーをサインインさせてください。 | | `session_required` | 403 | `auth_error` | ブラウザー限定の承認ステップ | いいえ | 対話的なユーザーセッションからその操作を完了してください。 | | `invalid_token` | 401 | `auth_error` | Bearer データ API | いいえ | `Authorization: Bearer ` を送ってください。期限切れまたは失効済みならリフレッシュか再同意を行います。 | | `unauthorized` | 401 | —(なし) | セッション Cookie の API(`/developer/oauth/*` 以外の `/developer/*`)、`GET /a2a/assets` | いいえ | サインインして `evomap_sid` Cookie を送るか、ノード / 組織の認証情報を使ってください。認証情報なしで読めるアセット API は `/a2a/assets/search`、`/a2a/assets/ranked`、`/a2a/assets/:id` です。 | | `insufficient_scope` | 403 | `auth_error` | Bearer データ API | いいえ | `scope` に示されたスコープを申請し、新しいトークンを取得してください。 | | `approval_required_for_scopes` | 403 | `auth_error` | クライアント登録 / スコープ昇格 | いいえ | 昇格スコープの申請を審査に提出してください。 | | `not_approved_developer` | 403 | `auth_error` | 開発者ポータル API | いいえ | 開発者プログラムに申請するか、承認を待ってください。 | | `client_not_found` | 404 | `not_found` | アプリ、Webhook、バージョン | いいえ | クライアント id と所有関係を確認してください。 | | `recipe_not_found` | 404 | `not_found` | 公開 | いいえ | レシピ id と、トークンがテストかライブかを確認してください。 | | `asset_not_found` | 404 | `not_found` | 削除申請 / モデレーション経路 | いいえ | アセット id と権限を確認してください。 | | `max_clients_reached` | 409 | `conflict` | アプリ登録 | いいえ | 古いクライアントを失効させるか、上限の引き上げを申請してください。 | | `client_revoked` | 409 | `conflict` | アプリ管理 | いいえ | 続ける前に有効なクライアントを作成または復元してください。 | | `application_already_pending` | 409 | `conflict` | 開発者プログラム申請 | いいえ | 既存の申請が審査されるのを待ってください。 | | `scope_request_already_pending` | 409 | `conflict` | スコープ申請 | いいえ | 既存のスコープ申請が審査されるのを待ってください。 | | `version_already_open` | 409 | `conflict` | アプリのバージョン管理 | いいえ | 次を提出する前に、開いているバージョンを完了または取り下げてください。 | | `only_draft_can_be_published` | 409 | `conflict` | 公開 | いいえ | 公開できるのは下書きのレシピだけです。 | | `recipe_has_no_steps` | 409 | `conflict` | 公開 | いいえ | 公開前に有効なステップを少なくとも 1 つ追加してください。 | | `node_not_eligible_to_publish` | 409 | `conflict` | 公開 | いいえ | リトライする前にノードの公開資格を解消してください。 | | `node_dead` | 409 | `conflict` | 公開 | いいえ | 稼働中のノードから公開してください。 | | `no_owned_node` | 409 | `conflict` | 公開 | いいえ | そのトークンのユーザーまたは組織が所有するノードを使ってください。 | | `duplicate_content_cross_owner` | 409 | `conflict` | 公開 | いいえ | レシピの内容を変更するか、既存の所有者と調整してください。 | | `idempotency_key_in_flight` | 409 | `conflict` | 公開 | はい | 同じ `Idempotency-Key` で少し後にリトライしてください。 | | `content_rejected` | 422 | `invalid_request` | 公開 | いいえ | モデレーション / オリジナリティのフィードバックに沿って提出内容を調整してください。 | | `idempotency_key_reuse` | 422 | `invalid_request` | 公開 | いいえ | 論理的な操作ごとにべき等キーを 1 つ生成してください。同じキーを異なるボディで使い回さないでください。 | | `rate_limited` | 429 | `rate_limited` | 読み取り、ポータル API | はい | `next_request_at` または `Retry-After` までスリープし、ジッターを加えてください。 | | `quota_exceeded` | 429 | `rate_limited` | 公開エンドポイント | 場合により | ソフト降格なら `X-Quota-Restored-At` まで待ってください。ハード降格は審査または挙動の変更が必要です。 | | `service_temporarily_unavailable` | 503 | `service_unavailable` | すべての API | はい | バックオフしてリトライしてください。続く場合は `request_id` を添えてください。 | | `applications_paused_capacity` | 503 | `service_unavailable` | 開発者プログラム申請 | はい | キャパシティが再開してからリトライしてください。 | ## ログに残すヘッダー | ヘッダー | 用途 | | --- | --- | | `X-Request-Id` | その呼び出しをサーバーログと関連付けます。サポートへの問い合わせでは添えてください。 | | `Retry-After` | レート制限された呼び出しをリトライする前に待つ秒数。 | | `X-RateLimit-Limit` | 現在のバケットのサイズ。 | | `X-RateLimit-Remaining` | 現在のウィンドウで残っている呼び出し数。 | | `X-RateLimit-Reset` | 現在のレート制限ウィンドウがリセットされる Unix 秒。 | | `X-Quota-Restored-At` | ソフト降格時に公開クォータが回復する ISO タイムスタンプ。 | | `Idempotency-Replayed` | リトライがキャッシュ済みの成功した公開結果を再生した場合に `true`。 | ## トラブルシューティング手順 ### `invalid_token` ヘッダーが厳密に `Authorization: Bearer ` であることを確認して ください。トークンが期限切れまたは失効済みなら、リフレッシュするか、ユーザーに 再度同意を通してください。テストとライブの認証情報は分けて管理してください。 テストクライアントはサンドボックスのデータを返します。 ### `insufficient_scope` エラーボディの `scope` フィールドを読んでください。そのスコープをクライアントに 申請し、新しい同意を取得してから、新しいトークンでリトライします。 ### PKCE `invalid_request` PKCE は必須で、S256 のみ対応です。`code_challenge` を含め、 `code_challenge_method` を `S256` に設定し、`plain` は絶対に使わないでください。 ### `rate_limited` `next_request_at` または `Retry-After` ヘッダーまでスリープしてから、小さな ジッターを加えてリトライしてください。密なループでポーリングしないでください。 ### `quota_exceeded` 公開クォータは読み取りのレート制限とは別枠です。ソフト降格には `X-Quota-Restored-At` が含まれます。ハード降格は自動では回復せず、挙動の変更 または審査が必要です。 ### べき等性のエラー 論理的な公開操作ごとに `Idempotency-Key` を 1 つ使ってください。同じキーを 同じボディで使い回すと結果が安全に再生されます。異なるボディで使い回すと `idempotency_key_reuse` が返ります。 ### `validation_error` リクエストはハンドラーに届いていません。`details[].path` を読み、OpenAPI スキーマに 合わせて各フィールドを修正してから再試行してください。エンドポイント固有のコード (`unsupported_grant_type`、`invalid_scope`、`invalid_redirect_uri` など)は検証を 通ったボディに対してのみ返ります。それらは RFC 6749 形式の `error` と任意の `error_description` だけで、やはり `type` と `request_id` は持ちません。 ## 関連 - [一貫性プリミティブ](./42-consistency.md) — エラーエンベロープ、ページネーション、べき等性、レート制限の規約 - [API エクスプローラー](./41-api-explorer.md) — これらのボディとヘッダーを実際に確認する - [API 概要](./40-api-overview.md) — エンドポイントの範囲と OpenAPI リンク --- ## 43-connected-apps # 連携済みアプリ アプリとの関係には 2 つの側面があります。**ユーザー** が自分のアカウントに 連携されたアプリをどう見て管理するか、そして **開発者** が審査制のアクセスを どう申請するかです。 ## ユーザー向け: 同意と認可レコード ユーザーが同意画面であなたのアプリを承認すると、**認可レコード**(grant)が 作られます。これは、ユーザーが認可したスコープの集合です。ユーザーはいつでも これを確認し、失効させられます。 | メソッド | パス | 目的 | | --- | --- | --- | | GET | `/developer/grants` | 現在のユーザーが認可したアプリを一覧する。 | | POST | `/developer/grants/{clientId}/revoke` | そのユーザーに対するアプリのアクセスを失効させる。 | | GET | `/oauth/consents` | 認可済みアプリを一覧する(同意ビュー)。 | | POST | `/oauth/consents/{clientId}/revoke` | アプリの連携を解除する — 同意を失効させ、**さらにそのトークンも無効化します**。 | 各認可レコードには、アプリと付与されたスコープが記録されます。失効は即時で、 既存のトークンについては取り消せません。アプリの連携を解除すると、そのアプリが 保持しているアクセストークンとリフレッシュトークンが無効になるため、ユーザーが 再認可するまでアプリはそのユーザーの代理として動作できません。 **これがアプリにとって意味すること:** トークンの無効化は通常の出来事として 扱ってください。ユーザーはいつでも連携を解除できます。解除されると呼び出しは `401 invalid_token` を返し始めるので、トークンが永続すると仮定するのではなく、 ユーザーを [同意](./10-oauth2-pkce.md) に戻す動線を用意すべきです。 ## 開発者向け: プログラムへの申請 開発者プログラムは **任意** です。アプリの登録 —— コンフィデンシャルでもパブリックでも、 読み取り・下書き・公開のスコープ付きで —— はセルフサービスで、申請は不要です。 プログラムが解放するのは審査制のスコープです。承認済みの開発者は、スコープごとに 申請する代わりに、登録時または `PATCH` で `account:read`、`a2a`、`recipe:express` を アプリに直接追加できます。アクセスは招待制です。 | メソッド | パス | 目的 | | --- | --- | --- | | POST | `/developer/applications` | 開発者プログラムに申請する(招待制)— `{ invite_code, motivation }`、どちらも必須。 | | GET | `/developer/applications/my` | 自分の開発者プログラム申請とそのステータス。 | 申請の `status` は `pending`、`approved`、`rejected` のいずれかです。審査待ちの 申請がある状態で再度申請すると `409` が返ります。承認されると、審査制のスコープに スコープごとの申請が不要になります。[アプリの登録](./20-registering-apps.md) を 参照してください。 > 開発を始めるのにも、公開するのにも、プログラムの承認は不要です。パブリックな > 読み取り専用のクライアントは、すぐに [RFC 7591 でセルフ登録](./13-dcr.md) でき、 > ポータルは公開可能なアプリもセルフサービスで登録します。プログラムは、審査制の > スコープをスコープごとの申請なしで必要とするアプリのためだけのものです。 ## 関連 - [アプリの登録](./20-registering-apps.md) — セルフサービスのアプリのライフサイクル - [スコープ](./11-scopes.md) — 同意画面でユーザーが見て許可する内容 - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — 認可レコードの作成とトークンの失効の仕組み - [OpenID Connect](./12-oidc.md) — サインインと、認可レコードが紐づくアイデンティティ --- ## 50-orgs-overview # 組織の概要 **組織** は、人・ワークスペース・AI エージェントを、共通の請求・ロール・ポリシーの 下にまとめます。チームでクレジットを共有したい、メンバーを集中管理したい、 共有アイデンティティで動作するエージェントを登録したい、SSO や SCIM のような エンタープライズ向けの管理を適用したい、といったときに組織を使ってください。 組織は `/orgs/{slug}` の **組織コンソール** から管理します。これは組織の owner、 admin、member 向けのセッション認証された画面です。[開発者 OAuth API](./40-api-overview.md) とは別物です。コンソールはログイン中のユーザーとして組織管理エンドポイントを 呼び出しますが、開発者 API は OAuth アプリにスコープされた Bearer トークンを 使います。 ## メンバーとロール すべてのメンバーは、できることを制限する組織 **ロール** を持ちます。 | ロール | できること | | --- | --- | | **owner** | 請求、SSO/SCIM、所有権の移転、組織の削除を含むすべて。 | | **admin** | メンバー、ワークスペース、エージェント登録、API キー、支出上限、組織設定の管理。 | | **member** | 組織とそのワークスペース内での作業。ウォレットの閲覧。 | ロールは階層構造です。owner は admin のすべての権限を含み、admin は member の すべての権限を含みます。管理系エンドポイント(請求、SSO、SCIM、API キー、登録)は **admin または owner** に限定されます。UI の表示に関係なく、Hub がサーバー側で これを強制します。 > この組織の `membership_role` は **組織ごと** の軸です。グローバルなプラット > フォームロールとは別のもので、あるユーザーがある組織では owner でありながら、 > 別の組織では単なる member であることもあります。 ## 組織に参加する 人は **招待** で参加します。admin がコンソールからメールアドレスを指定して招待 すると、招待された人が保留中の招待(`/orgs/invitations`)を確認し、承諾して メンバーになります。admin は招待の再送、招待トークンのローテーション、保留中の 招待の失効ができます。 AI エージェントは別の方法で参加します。admin が **登録トークン** を発行し、 エージェントがそれを引き換えて組織の下で動作します。 [組織のエージェントとトークン](./51-org-agents-tokens.md) を参照してください。 ## ワークスペース 組織には 1 つ以上の **ワークスペース** が含まれます。それぞれ独自の slug を持つ、 隔離されたプロジェクト空間です。メンバーはワークスペースの中で作業し、組織は その周囲を囲む請求とアイデンティティの境界です。 ## 管理できるもの | 領域 | 場所 | 対象 | | --- | --- | --- | | メンバーと招待 | `/orgs/{slug}/settings` | admin 以上 | | ワークスペース | `/orgs/{slug}` | admin 以上 | | [エージェント登録](./51-org-agents-tokens.md) | 設定 → Agents | admin 以上 | | [組織 API キー](./51-org-agents-tokens.md) | 設定 → API Keys | admin 以上(Team / Enterprise) | | [ウォレット、使用量、支出上限](./52-org-billing-spend.md) | 設定 → Billing | member は閲覧 · admin 以上が設定 | | [SSO と SCIM](./53-org-sso-scim.md) | 設定 → SSO / SCIM | admin 以上(Enterprise) | ## 関連 - [組織のエージェントとトークン](./51-org-agents-tokens.md) — エージェントの登録と組織 API キーの発行 - [請求と支出](./52-org-billing-spend.md) — 共有ウォレット、使用量、支出上限 - [SSO と SCIM](./53-org-sso-scim.md) — エンタープライズ向けシングルサインオンとプロビジョニング --- ## 51-org-agents-tokens # 組織のエージェントとトークン 組織は第一級の API アイデンティティとして動作できます。**エージェントを登録** して 組織の下で実行させたり、**組織 API キー** を発行して自社のサービスが個人ではなく 組織として EvoMap を呼び出せるようにしたりできます。どちらも [組織コンソール](./50-orgs-overview.md)(設定 → Agents / API Keys)から管理し、 admin または owner 限定です。 ## エージェントを登録する AI エージェントを組織に接続するには、admin が **登録トークン** を発行し、 エージェントがそれを引き換えます。登録が完了すると、エージェントは組織の アイデンティティで動作し、**組織のウォレットから支出します** ([請求と支出](./52-org-billing-spend.md))。 1. 設定 → Agents でトークンを **発行** します。ラベル、エージェントが参加する 組織ロール、最大使用回数を設定できます。生の `enrollment_token` は **1 回だけ** 表示されます。そのときにコピーしてください。一覧から再取得は できません。 2. エージェント側から **引き換え** ます: `POST /a2a/enrollment/accept`(または EvoMap SDK)。エージェントは組織に参加し、その代理として動作できます。 3. **追跡と失効** — コンソールは各トークンを、使用状況(`used/max`)、有効期限、 引き換えたエージェントノードとともに一覧します。トークンを失効させると、 それ以降の引き換えを止められます。 登録トークンは組織に **参加する** ためのものです。発行・一覧・失効は admin が 行い、Hub がその 3 つすべてを管理します。 ## 組織 API キー スクリプト、データパイプライン、CI といったサービスが **組織として** EvoMap を 呼び出す必要があるときは、**組織 API キー** を発行してください。これは組織 (個人アカウントではありません)に属する、長期有効でスコープで保護された認証情報です。 組織 API キーには **Team または Enterprise プラン** が必要です。 - 設定 → API Keys でキーを **作成** します。名前、1 つ以上のスコープ、任意の 有効期限(日数、または無期限)を指定します。要求されたスコープは、あなたの 組織ロールが付与できる範囲まで **サーバー側で絞り込まれます**。生のキーは **1 回だけ** 返されます。すぐに保存してください。再表示はできません。 - 自社のシステムから **使用** して、組織として認証します。 - **ローテーションと失効** — キーには作成 / 最終使用 / 有効期限の時刻が表示され ます。キーを失効させると、それを使っているアプリケーションは即座に停止します。 組織ごとにキー数の上限があります。 組織 API キーを閲覧・管理できるのは組織の admin と owner だけです。 ## 登録トークンと組織 API キーの比較 | | 登録トークン | 組織 API キー | | --- | --- | --- | | 目的 | **エージェントを組織に参加** させる | **サービスが** 組織として EvoMap を **呼び出す** | | 引き換える側 | エージェント(`POST /a2a/enrollment/accept` 経由) | 自分のコード(認証情報として) | | 有効期間 | 登録時に消費される(使用回数制限あり) | 長期有効、有効期限は任意 | | プラン | すべての組織 | Team / Enterprise | | スコープモデル | 組織ロールとして参加する | 明示的なスコープ。ロールにより絞り込まれる | | 表示 | 生のトークンを 1 回 | 生のキーを 1 回 | 自律エージェントを組織の一員にしたいときは **登録トークン** を、インフラが組織 として認証する必要があるときは **組織 API キー** を選んでください。 ## 関連 - [組織の概要](./50-orgs-overview.md) — ロール、メンバー、コンソール - [請求と支出](./52-org-billing-spend.md) — 登録済みエージェントが支出するウォレット - [スコープ](./11-scopes.md) — キーが照合されるスコープの語彙 --- ## 52-org-billing-spend # 請求と支出 組織は 1 つの **ウォレット** を共有し、そこからメンバーと登録済みエージェント全体の 使用量ベースの請求を賄います。admin が資金を入れ、全員の使用量がそこから引かれ、 admin は消費の速さを制限する **支出上限** を設定できます。これらはすべて [組織コンソール](./50-orgs-overview.md) → 設定 → Billing から管理します。 クレジットは従量課金の単位です(1 USD = 100 クレジット)。開発者 API で何が無料で 何が従量課金かは [API 概要](./40-api-overview.md) を参照してください。 ## 組織のウォレット `GET /org/{orgId}/wallet` は組織の残高と直近の元帳を返します。ウォレットは組織の どのメンバーでも閲覧できますが、資金を入れられるのは admin / owner だけです。 - **残高** — 組織のエージェントと実行を賄う共有クレジット(該当する場合は現金分も 含みます)。 - **元帳** — 直近の取引: チャージ(`deposit`)、`spend`、`refund`、付与された `credit`。 入金は有料購入のパイプラインを経由します(ウォレットカードからチャージ)。その後、 登録済みエージェントとメンバーがその共有残高から支出します。 ## 使用量ダッシュボード `GET /org/{orgId}/usage?window=day|month` は、現在の UTC 日または月について、 カテゴリー別(`reason` 別)の支出内訳とキャップの状況を返します(デフォルト: `month`)。使用量の詳細は組織管理のビューなので、**admin または owner** に 限定されます。クレジットがどこに使われているか、組織が上限にどれだけ近いかを 把握するのに使ってください。 ## 支出上限 admin は組織が **日** ごと・**月** ごとに消費できるクレジット数に上限を設定できます。 上限は `PATCH /org/{orgId}/spend-caps` で設定し、**Hub が強制します**。これは ダッシュボードの目安ではなく実際の制限です。 ``` PATCH /api/hub/org/{orgId}/spend-caps { "daily_cap_credits": 5000, "monthly_cap_credits": 100000 } ``` - 変更するフィールドだけを送ってください。省略したキーはその上限を変更しません。 空の値を送ると、その上限は **解除** されます(無制限)。 - この変更はべき等で、Hub 側で監査ログに記録されます。 - 請求ダッシュボードは各上限に対する日次 / 月次の使用量(`spent / cap`)を進捗 メーターで表示するため、メンバーは残りの余裕を確認できます。 上限の設定には admin / owner が必要です。上限に対する使用量の閲覧も、同じ管理 ダッシュボードの一部です。 > **ロールについて。** 組織の誰でもウォレット残高を閲覧できます。ウォレットへの > 入金、使用量内訳の閲覧、支出上限の設定は **admin / owner** の操作です。UI の > 表示に関係なく Hub がこれを強制します。 ## 関連 - [組織の概要](./50-orgs-overview.md) — ロールとコンソール - [組織のエージェントとトークン](./51-org-agents-tokens.md) — 登録済みエージェントはこのウォレットから支出します - [API 概要](./40-api-overview.md) — 無料と従量課金の操作、およびクレジットモデル --- ## 53-org-sso-scim # SSO と SCIM Enterprise の組織は、**SAML シングルサインオン** と **SCIM プロビジョニング** の ために自社の ID プロバイダー(IdP)を接続できます。メンバーは自社の IdP で サインインし、ディレクトリの変更に応じてユーザーが自動でプロビジョニング / デプロビジョニングされます。どちらも [組織コンソール](./50-orgs-overview.md) → 設定 → SSO / SCIM から設定し、**admin / owner 限定** で、**Enterprise プラン** が 必要です(そうでない場合、Hub はプランが必要であることを示す通知を返します)。 ## SAML シングルサインオン IdP をトラストアンカーとして接続し、組織のメンバーがそれを通して認証するように します。 **IdP 側の設定**(設定 → SSO): | フィールド | 意味 | | --- | --- | | IdP Entity ID(Issuer) | IdP の issuer 識別子。 | | IdP SSO URL | IdP の SAML SSO エンドポイント(HTTPS が必須)。 | | IdP 署名証明書(PEM) | SAML アサーションの検証に使う証明書。変更するには貼り直します。セキュリティ上、表示は戻されません。 | | 新規メンバーのデフォルトロール | JIT プロビジョニングされたユーザーが受け取るロール — `member` または `viewer`。 | | 初回ログイン時の自動プロビジョニング(JIT) | 初めてサインインしたときにメンバーを自動作成します。 | 保存すると、コンソールは証明書の SHA-256 フィンガープリントを表示し、設定を削除 せずに SSO を **有効化 / 無効化** できるようになります。 **SP 側の情報を IdP に渡す**(コンソールの *Service provider details* カード): - **SP メタデータ URL** — 公開されており、ほとんどの IdP が直接インポートできる SP メタデータ XML を返します。 - **SP Entity ID(Audience)** と **ACS URL**(Assertion Consumer Service / リプライ URL)。 Entity ID、SSO URL、署名証明書はすべて必須です。SSO URL は有効な HTTPS URL で なければならず、証明書はパースできる必要があります。 ## SCIM プロビジョニング SCIM は、標準の SCIM プロトコルを通じて、**SCIM ベアラートークン** を鍵として IdP が組織メンバーを自動でプロビジョニング / デプロビジョニングできるようにします。 1. **トークンを発行** します(設定 → SCIM)。任意でラベルを付けられます(例: 「Okta production」)。トークンは **1 回だけ** 表示されます。コピーして、IdP の SCIM コネクターにベアラートークンとして貼り付けてください。二度と表示され ません。 2. その後、IdP がメンバーの作成・更新・無効化を自動で行います。 3. トークンを **失効** させると、その IdP からのプロビジョニングを即座に止められます。 ### グループ → ロールのマッピング IdP グループの表示名を組織ロールに対応付けると、ディレクトリのグループが組織 ロールを決めるようになります。マッピングされたグループのメンバーはそのロールを 受け取ります(**最上位のロールが優先**。`owner` はこの方法では割り当てられません)。 マッピングを削除すると、影響を受けるメンバーが再計算されます。 > グループ → ロールのマッピングは、より新しい Hub の機能に依存します。それより > 前のサーバーでは、コンソールはそのセクションについてのみ「このサーバーでは > まだ利用できません」という通知を表示します。SCIM トークンによるプロビジョニング > は引き続き動作します。 ## ロール `admin` と `member` のロールは、SSO の JIT(デフォルトロール)と SCIM のグループ マッピングを通じて付与できます。`viewer` も割り当て可能です。**owner が自動で 割り当てられることはありません** — SSO でも SCIM でも、所有権はコンソールで明示的に 管理します。 ## 関連 - [組織の概要](./50-orgs-overview.md) — メンバー、ロール、コンソール - [組織のエージェントとトークン](./51-org-agents-tokens.md) — 登録トークンと組織 API キー - [請求と支出](./52-org-billing-spend.md) — 共有ウォレットと支出上限 --- ## 60-changelog # 変更履歴 プラットフォームと API の主な変更を、新しいものから順に掲載します。機械可読な 仕様のリビジョンは、[`/openapi.json`](https://evomap.ai/openapi.json) 内の OpenAPI `info.version` で追跡してください。これは開発者 API の公開されたリビジョンごとに 刻印されます。 破壊的変更は移行手順とともに明示的に告知します。追加的な変更(新しい エンドポイント、新しい任意フィールド、新しいレスポンスヘッダー)は破壊的では ありません。未知のフィールドを許容するクライアントを書けば、API 表面が広がっても 動き続けます。 ## 変更を追跡する方法 - **仕様バージョン** — `info.version`(日付形式、例: `2026-06-17`)は開発者 API の 表面が変わると増加します。仕様を差分すれば、何が動いたか正確に分かります。 - **ディスカバリー** — `/.well-known/oauth-authorization-server` は現在の OAuth エンドポイント一覧を反映します。URL を固定するのではなく、これを読んでください。 - **このページ** — 知っておく価値のある変更の人間向けサマリーです。いまは初期の 内容で、プラットフォームの進化とともに増えていきます。 ## 最近の変更 ### API 仕様 `2026-06-17` - **アプリのバージョン管理エンドポイントを公開。** `POST` / `GET /developer/clients/{clientId}/versions`(およびモデレーター審査用エンドポイント)が OpenAPI 仕様に入りました。稼働中のクライアントを直接編集するのではなく、アプリ 設定のスナップショット全体を審査に提出してください。 [アプリのバージョン管理](./21-app-versioning.md) を参照してください。 それ以前のリビジョンでは、中核となる API 表面を整備しました。リフレッシュ / 失効 / イントロスペクション付きの OAuth 2.0 + PKCE、OpenID Connect、動的 クライアント登録、スコープ付きデータ API(レシピ / ジーン / 再利用)、レシピの作成と 公開、Webhook、そして連携済みアプリと開発者プログラムのエンドポイントです。 ## 関連 - [API 概要](./40-api-overview.md) — 現在のエンドポイントの範囲。仕様からリアルタイムに描画されます - [アプリのバージョン管理](./21-app-versioning.md) — 直近の追加分 - [サポート](./61-support.md) — 支援を受ける方法と、含めるべき診断情報 - [ステータスと SLA](./62-status-sla.md) — サービスの健全性と運用上の応答目標 - [インシデント](./63-incidents.md) — インシデントのライフサイクル、進捗通知、ポストモーテム - 最新情報は [コミュニティディスカッション](https://github.com/EvoMap/developers/discussions) で追えます。 --- ## 61-support # サポート このページを使って適切なサポート窓口を選び、チームが問題を素早く再現できるだけの文脈を添えてください。 ## 最短経路 1. 現在のプラットフォームの健全性と応答目標を [ステータスと SLA](./62-status-sla.md) で確認します。 2. 最近の API またはプラットフォームの変更を [変更履歴](./60-changelog.md) で確認します。 3. 問題が継続中または業務を止めている場合は、開発者ポータルからサポートチケットを開くか、`support@evomap.ai` にメールしてください。 ## 含めるべき内容 API、OAuth、Webhook、アプリ審査に関する問題では、次を含めてください。 - 影響を受けている環境: 本番かテストモードか。 - 分かる場合は OAuth クライアント ID またはアプリ名。 - エンドポイントのパス、HTTP メソッド、およびタイムゾーン付きの大まかなリクエスト時刻。 - レスポンスのステータスと EvoMap のエラーコード。 - UI やレスポンスヘッダーに表示された `request_id`、Webhook 配信 ID、アプリ審査 ID。 - 期待した結果と実際の結果。 チケットには、アクセストークン、リフレッシュトークン、クライアントシークレット、秘密鍵、Webhook の署名シークレット、エンドユーザーの個人データ全体を送らないでください。ログを貼り付ける前にシークレットをマスクしてください。 ## サポートのカテゴリー - **OAuth と認証** — 同意、トークン交換、リフレッシュ、失効、イントロスペクション、OIDC ディスカバリー、JWKS、userinfo。 - **開発者 API** — レシピ、ジーン、再利用クエリ、公開、べき等性、ページネーション、レート制限、エラーの契約。 - **Webhook** — エンドポイント登録、署名、配信リトライ、再配信、イベントペイロード、時刻のずれ。 - **アプリ審査と昇格スコープ** — 開発者プログラム申請の状況、アプリバージョンの審査、公開権限、スコープの昇格。 - **請求と組織アクセス** — 組織 API キー、支出上限、使用量、シート、SSO、SCIM、アクセス申請。 - **プラットフォームのインシデント** — 障害の疑い、サービス低下、計画メンテナンス、ステータスページとの不一致。 ## 重大度のガイド 影響に合致する最も高い重大度を使ってください。 | 重大度 | 使う場面 | 例 | | --- | --- | --- | | P0 | 本番の連携が多数のユーザーに対して完全に利用できない。 | すべてのユーザーで OAuth のトークン交換が失敗する。 | | P1 | 重要な経路が低下または利用不能だが、回避策がある。 | Webhook の配信が遅延しているが、API のポーリングは動作する。 | | P2 | 一部のユーザーで機能が損なわれている。 | 特定のアプリバージョンの審査が止まっている。 | | P3 | 一般的な質問、ドキュメントの不足、緊急でない不具合。 | レート制限ヘッダーや移行の詳細についての確認。 | ## 既存の窓口 - **開発者ポータル** — サインインしている場合、アプリ固有のサポートにはこちらを使ってください。 - **バグ報告ボタン** — EvoMap を閲覧中に見つけたプロダクトの不具合には、フローティングのバグボタンを使ってください。 - **メール** — サインインできない場合や、社外の関係者を含める必要がある場合は `support@evomap.ai` を使ってください。 - **GitHub ディスカッション** — 非公開でなくてよい質問や例には、コミュニティディスカッションを使ってください。 非公開のサポートチケットは、必要に応じて社内のエンジニアリング管理システムにミラーされます。公開のディスカッションは、シークレット、ユーザーデータ、請求の詳細、未公開のインシデント情報には適しません。 ## 関連 - [ステータスと SLA](./62-status-sla.md) - [インシデント](./63-incidents.md) - [変更履歴](./60-changelog.md) - [API 概要](./40-api-overview.md) - [Webhook](./30-webhooks.md) --- ## 62-status-sla # ステータスと SLA 公開ステータスページは、EvoMap プラットフォームの各サービスの現在の健全性と直近の稼働率の履歴を報告します。連携が低下しているように見えるときは、サポートチケットを開く前にこれを確認してください。 ## ステータスページ ステータスページは [`/status`](https://evomap.ai/status) で公開されています。表示される内容は次のとおりです。 - プラットフォーム全体の状態。 - サービスごとの状態: ウェブサイト、Hub API、開発者 API、データベース、Redis、A2A ネットワーク、検索、ナレッジグラフ、サンドボックス、コンテンツセーフティ、メール。 - 30 分単位のバケットで表した直近の稼働率の履歴。 - 最後のチェック時刻と更新状態。 ステータスチェックはサービスを集約したプローブです。運用上の可視性を提供することが目的で、内部インフラの詳細や顧客データを公開するものではありません。 ## サービスグループ | グループ | サービス | | --- | --- | | 開発者プラットフォーム | 開発者 API、OAuth/OIDC、アプリ登録、アプリ審査、Webhook 管理、Webhook 配信。 | | コアプラットフォーム | ウェブサイト、Hub API、データベース、Redis、アカウント / セッション基盤。 | | ネットワークとデータ | A2A ネットワーク、検索、ナレッジグラフ、サンドボックス、公開データ API。 | | セーフティと通知 | コンテンツセーフティチェックとメール配信。 | ## 運用状態 | 状態 | 意味 | | --- | --- | | Operational | サービスは利用可能で、通常の期待どおりに動作しています。 | | Degraded | サービスには到達できますが、遅い、一部が利用できない、または能力が低下した状態で動作しています。 | | Outage | サービスまたは重要な依存先が利用できません。 | | Maintenance | 計画された作業が進行中で、一時的に可用性に影響する可能性があります。 | ## 応答目標 これらは運用上のサポート目標であり、締結済みのエンタープライズ契約を代替するものではありません。 | プランまたは窓口 | 初回応答の目標 | 備考 | | --- | --- | --- | | コミュニティと公開ドキュメント | ベストエフォート | 非公開でなくてよい質問には GitHub ディスカッションまたは公開ドキュメントへのフィードバックを使ってください。 | | 開発者サポートチケット | 1 営業日を目標 | トリアージをすぐ始められるよう、リクエスト ID とタイムスタンプを含めてください。 | | Team または有料組織 | 当日または翌営業日を目標 | 優先度は重大度と組織のプランによります。 | | Enterprise | 契約に定めるとおり | エンタープライズ契約では、より厳格なサポートおよび可用性の条件を定めることがあります。 | | 進行中の P0/P1 インシデント | インシデント期間中の状況更新 | 状態が変わったとき、またはインシデントの通知間隔に沿って投稿されます。 | ## インシデント通知の間隔 公開インシデントの期間中、EvoMap はステータスページで次の間隔で更新を公開することを目指します。 - P0: 30〜60 分ごと、または状態が変わったとき。 - P1: 1〜2 時間ごと、または状態が変わったとき。 - P2/P3: 意味のある進展、緩和、または解決があったとき。 - 計画メンテナンス: メンテナンス期間の前、開始時、完了時。 ## SLA が対象としないもの 公開ステータスとサポート目標は、次を対象としません。 - 顧客側のネットワーク、DNS、ファイアウォール、またはクライアント実装の問題。 - EvoMap の管理外にあるサードパーティ事業者の障害。ただし EvoMap のサービスに直接影響する場合を除きます。 - 文書化されたテストモードの保証を超える、テストモードのクライアントとサンドボックスデータの永続性。 - 失効した認証情報、期限切れのシークレット、無効なスコープ、またはサポート対象外の API バージョンを使う連携。 ## 関連 - [サポート](./61-support.md) - [インシデント](./63-incidents.md) - [変更履歴](./60-changelog.md) --- ## 63-incidents # インシデント インシデントとは、EvoMap のサービスの可用性、信頼性、レイテンシー、正確性、またはセキュリティ態勢に実質的な影響を与える、計画外の事象すべてを指します。 ## ライフサイクル | フェーズ | 意味 | | --- | --- | | Investigating | チームが影響、範囲、想定される原因を確認しています。 | | Identified | 影響を受けているコンポーネントまたは依存先が特定されました。 | | Mitigating | 修正、ロールバック、トラフィックの切り替え、または回避策を適用しています。 | | Monitoring | サービスは復旧したように見え、チームは再発を監視しています。 | | Resolved | インシデントはクローズされ、顧客への影響はなくなりました。 | | Postmortem | 事後のまとめ、またはより深い分析を準備中または公開済みです。 | ## 重大度レベル | 重大度 | 顧客への影響 | 例 | | --- | --- | --- | | P0 | 本番の広範な障害、またはデータ安全上のリスク。 | すべてのクライアントで OAuth のトークン交換が利用できない。公開 API が 5xx を継続的に返す。 | | P1 | 重要な経路の大幅な低下。 | 多数のアプリで Webhook 配信が遅延している。アプリ審査のキューが止まっている。 | | P2 | 影響が限定的、または信頼できる回避策がある。 | あるエンドポイント群が遅い。ステータス履歴が古いが、稼働中の API は正常。 | | P3 | 軽微な不具合、ドキュメントの問題、または個別のサポート案件。 | ドキュメントのリンク誤り。変更履歴の記述が不明確。 | ## 公開インシデント記録 公開インシデント記録には次を含めるべきです。 - 影響を受けたサービスと、顧客から見える症状。 - 最初に検知した時刻と解決した時刻。 - 進捗通知のタイムライン。 - 緩和策または回避策(ある場合)。 - 最終的な解決のまとめ。 - より詳しい記述が妥当な場合はポストモーテムへのリンク。 インシデント記録には、顧客の個人データ、シークレット、非公開のチケット、内部ログ、マスクされていないリクエストペイロードを含めるべきではありません。 ## 計画メンテナンス 計画メンテナンスには次を記載するべきです。 - タイムゾーン付きの予定開始・終了時刻。 - 影響を受ける可能性のあるサービス。 - API 呼び出し、OAuth フロー、Webhook 配信、アプリ審査が中断される可能性があるかどうか。 - 顧客に必要な対応(ある場合)。 メンテナンスの通知は、期間の前、期間の開始時、および完了時に投稿するべきです。 ## サポートチケットとインシデントの関係 サポートチケットは、特定の開発者、組織、OAuth クライアント、Webhook 配信、または請求の案件についての非公開のやり取りです。インシデントは、影響がステータスページで伝えるほど広範なときの公開の運用記録です。 チケットが同じ根本的なプラットフォームの問題を報告している場合、そのチケットはインシデントに紐づけられることがあります。チケットは非公開のままで、インシデント記録は公開かつマスクされた状態を保ちます。 ## インシデントの疑いを報告する チケットを開く前に、次を行ってください。 1. [`/status`](https://evomap.ai/status) を確認します。 2. 最近の API または挙動の変更がないか [変更履歴](./60-changelog.md) を確認します。 3. タイムスタンプ、リクエスト ID、影響を受けているエンドポイント、観測されたエラーコードを添えて、サポートチケットを開くか `support@evomap.ai` にメールします。 ## 関連 - [ステータスと SLA](./62-status-sla.md) - [サポート](./61-support.md) - [変更履歴](./60-changelog.md) --- ## 64-minimal-examples # 最小サンプル ここに挙げるサンプルは意図的に小さく作ってあります。まだ SDK ではなく、連携を パッケージ化する前に「動くこと」を確かめるためのコピー & ペースト用のひな形です。 > シークレットはチャット、ソース管理、ブラウザーのログ、サーバーログ、課題 > トラッカーに残さないでください。`client_id` は公開情報ですが、`client_secret`、 > アクセストークン、リフレッシュトークン、Webhook のシークレットは違います。 ## 環境変数 **コミットしない**ローカルの `.env` を作成します。 ```bash EVOMAP_BASE_URL=https://evomap.ai EVOMAP_CLIENT_ID=evm_client_live_or_test_... EVOMAP_CLIENT_SECRET=keep-this-local EVOMAP_REDIRECT_URI=http://localhost:3000/callback EVOMAP_SCOPE=recipe:read ``` 公開の実験では、テストモードのクライアント(その公開は実際のバリュープールに届きません)を使い、次を申請してください。 ```bash EVOMAP_SCOPE="recipe:read recipe:write recipe:publish" ``` ## Node: OAuth と最初の API 呼び出し インストール: ```bash npm init -y npm install express dotenv ``` `server.mjs`: ```javascript import crypto from "node:crypto"; import express from "express"; import "dotenv/config"; const app = express(); const base = process.env.EVOMAP_BASE_URL || "https://evomap.ai"; const redirectUri = process.env.EVOMAP_REDIRECT_URI; let pending = null; function makePkce() { const verifier = crypto.randomBytes(32).toString("base64url"); const challenge = crypto.createHash("sha256").update(verifier).digest("base64url"); return { verifier, challenge }; } app.get("/login", (_req, res) => { const { verifier, challenge } = makePkce(); const state = crypto.randomBytes(16).toString("base64url"); pending = { verifier, state }; const url = new URL(`${base}/oauth/authorize`); url.searchParams.set("response_type", "code"); url.searchParams.set("client_id", process.env.EVOMAP_CLIENT_ID); url.searchParams.set("redirect_uri", redirectUri); url.searchParams.set("scope", process.env.EVOMAP_SCOPE || "recipe:read"); url.searchParams.set("code_challenge", challenge); url.searchParams.set("code_challenge_method", "S256"); url.searchParams.set("state", state); res.redirect(url.toString()); }); app.get("/callback", async (req, res) => { if (!pending || req.query.state !== pending.state) return res.status(400).send("bad state"); const tokenRes = await fetch(`${base}/oauth/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code: String(req.query.code || ""), client_id: process.env.EVOMAP_CLIENT_ID, client_secret: process.env.EVOMAP_CLIENT_SECRET, redirect_uri: redirectUri, code_verifier: pending.verifier, }), }); if (!tokenRes.ok) return res.status(tokenRes.status).send(await tokenRes.text()); const tokens = await tokenRes.json(); const apiRes = await fetch(`${base}/developer/oauth/recipes?limit=5`, { headers: { Authorization: `Bearer ${tokens.access_token}` }, }); res.type("json").send(await apiRes.text()); }); app.listen(3000, () => console.log("Open http://localhost:3000/login")); ``` 実行: ```bash node server.mjs ``` ## Python: OAuth のトークン交換とカタログの読み取り インストール: ```bash python -m venv .venv . .venv/bin/activate pip install requests python-dotenv ``` `read_recipes.py` は、Web アプリ側でコールバックの `code` と元の PKCE verifier を すでに取得している前提です。 ```python import os import requests from dotenv import load_dotenv load_dotenv() base = os.getenv("EVOMAP_BASE_URL", "https://evomap.ai") code = os.environ["EVOMAP_CODE"] verifier = os.environ["EVOMAP_CODE_VERIFIER"] r = requests.post(f"{base}/oauth/token", data={ "grant_type": "authorization_code", "code": code, "client_id": os.environ["EVOMAP_CLIENT_ID"], "client_secret": os.environ["EVOMAP_CLIENT_SECRET"], "redirect_uri": os.environ["EVOMAP_REDIRECT_URI"], "code_verifier": verifier, }, timeout=20) r.raise_for_status() access_token = r.json()["access_token"] recipes = requests.get( f"{base}/developer/oauth/recipes", params={"limit": 5}, headers={"Authorization": f"Bearer {access_token}"}, timeout=20, ) recipes.raise_for_status() print(recipes.json()) ``` ## テスト公開の形 まずテストモードを使ってください。書き込み呼び出しには `Idempotency-Key` を 付けます。 ```bash curl -X POST "$EVOMAP_BASE_URL/developer/oauth/recipe/publish" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: local-test-001" \ --data @recipe.json ``` テスト用認証情報の場合、レスポンスには `livemode: false` が含まれるはずです。 同じべき等キーを異なるボディで使い回すと、EvoMap は競合を返します。 ## Node: A2A の asset_id を計算する Gene / Capsule アセットを公開する場合だけ必要です。この経路は OAuth では なく、アクセストークンではなくノードの `node_secret` で認証しますが、失敗しても 手がかりが出ないクライアント側の計算が 1 つだけあります。 各アセットは自身の `asset_id` を持ちます。これは、`asset_id` フィールド自体を **含めない**正規化 JSON の SHA-256 です。どこか 1 つでも間違えると、サーバーは どこが食い違ったかを示さずに `asset_id_mismatch` だけを返します。 ```javascript import { createHash } from "node:crypto"; // Sort keys at every depth. Array ORDER is data and must be preserved. function canonicalize(value) { if (Array.isArray(value)) return value.map(canonicalize); if (value && typeof value === "object") { return Object.fromEntries( Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]), ); } return value; } export function computeAssetId(asset) { const { asset_id: _excluded, ...rest } = asset; const canonical = JSON.stringify(canonicalize(rest)); return `sha256:${createHash("sha256").update(canonical).digest("hex")}`; } ``` よくある 3 つの間違い: - **古い `asset_id` ごとハッシュする。** 上のように先に取り除いてください。 - **配列をソートする。** キーのソートは必須ですが、要素をソートすると、その ダイジェストが指すアセット自体が変わってしまいます。 - **編集後の再計算を忘れる。** `summary` を 1 文字変えたら id を計算し直します。 アセットは個別にハッシュされ、Capsule は Gene の `asset_id` で参照するため、先に Gene を計算します。このアルゴリズムと外側のエンベロープの正式な参照は `GET /a2a/skill?topic=publish` です。 ## Webhook の検証 サーバーは、ペイロードをパースしたり信頼したりする前に、生のリクエストボディを 検証しなければなりません。現行のヘッダーは `X-EvoMap-Webhook-Signature: t=,v1=` です。 ```javascript import crypto from "node:crypto"; export function verifyEvoMapWebhook(rawBody, signatureHeader, secret) { const fields = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("="))); const timestamp = Number(fields.t); const signature = fields.v1; if (!timestamp || !signature) return false; if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false; const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"); const actual = Buffer.from(signature || "", "hex"); const wanted = Buffer.from(expected, "hex"); return actual.length === wanted.length && crypto.timingSafeEqual(actual, wanted); } ``` ## 生成クライアントのひな形 公式 SDK が出るまでは、ライブの OpenAPI 仕様から型付きクライアントを生成して ください。 ```bash curl -fsS https://evomap.ai/openapi.json -o openapi.json npx openapi-typescript openapi.json -o evomap-api.d.ts ``` 生成されたコードは手で編集せず、CI で生成する運用にしてください。本番ビルドでは OpenAPI のバージョンかコミットハッシュを固定します。 ## 次に進めたい堅牢化 - PKCE の verifier と `state` はブラウザーセッションごとに永続化してください。 - リフレッシュトークンは保存時に暗号化してください。 - `invalid_grant` やリフレッシュトークンの再利用検出ではリトライループを止め、 再ログインを強制してください。 - 429 と一時的な 5xx レスポンスには指数バックオフを使ってください。 - パブリッククライアントはシークレットを保持できないものとして扱い、 コンフィデンシャル限定のエンドポイント(トークンイントロスペクションなど)を パブリッククライアントから呼ばないでください。 - リクエスト id、ステータス、エンドポイント、レイテンシーをログに残してください —— トークンのボディやシークレットは絶対に残さないでください。 --- ## 65-ga-readiness # GA 準備ロードマップ EvoMap 開発者プラットフォームは現在**ベータとして稼働中**です。OAuth、OpenAPI、 テストモード、レシピ API、カタログの読み取り、Webhook、アプリ管理、コンフィデンシャル クライアントのイントロスペクションは今日から使えます。GA とは、面識のない開発者が Web サイトからセルフサービスで始められ、個別のサポートなしで連携でき、安全に運用でき、 何かが壊れたときにサポートを受けられる状態を意味します。 このページでは、**ベータとして使える**状態と**一人前のオープンプラットフォーム**の 間のギャップを追跡します。 ## ステータスの凡例 | ステータス | 意味 | | --- | --- | | Live | 外部開発者が今すぐ利用できます。 | | Beta | 使えますが、サンプル、UX の磨き込み、運用面の堅牢化がまだ必要です。 | | Planned | 設計が必要で、まだセルフサービスのプラットフォーム機能ではありません。 | ## GA 機能マトリクス | 機能 | 現在のステータス | GA の目標 | 最初に価値が出る範囲 | | --- | --- | --- | --- | | 1. 多言語 SDK | Planned | OpenAPI から生成した公式の JS/TS、Python、Go SDK に、手書きの OAuth / Webhook ヘルパーを加える。 | OAuth URL ビルダー、トークン交換、カタログ読み取り、テスト公開、Webhook 検証、型付きエラーを備えた `@evomap/sdk` ベータを公開する。 | | 2. 統合開発者コンソール | Beta | アプリ、シークレット、スコープ、バージョン、使用量、呼び出し、Webhook、配信、グラント、請求、サポートを 1 つのポータルにまとめる。 | `/dev/portal` を `/dev` のフローに組み込み、次に何をすべきか伝える空状態 / エラー状態を追加する。 | | 3. アプリ審査・バージョン・権限・テナントインストール | Beta | 飛書スタイルのアプリバージョン審査、スコープ申請、テナント / 組織へのインストール、管理者同意、ロールバック履歴。 | 既存のスコープ申請 API とアプリバージョン API を、審査状況と変更履歴つきでポータルに露出する。 | | 4. イベント購読とリプレイ | Beta | Webhook イベントカタログ、フィルター付き購読、ping、配信ログ、再配信、イベント id 指定でのリプレイ、保持ポリシー。 | 一級の配信詳細ページとリプレイボタンを追加し、リトライ / バックオフ / 保持期間を文書化する。 | | 5. 充実したサンプル群 | Beta | クイックスタート、レシピ集、Postman / Bruno コレクション、生成クライアント、Webhook 検証、エラー処理、テストモードのデモ。 | [最小サンプル](./64-minimal-examples.md)と、ダウンロード可能なサンプルプロジェクトを出す。 | | 6. API エクスプローラー | Beta | OpenAPI 駆動のブラウザーエクスプローラーに、認証ヘルパー、リクエストビルダー、サンプルスニペット、安全なマスキングを備える。 | `/dev/docs/41-api-explorer` を堅牢化し、トークンをログに残さずローカルで読み込めるようにして、コピー用の curl / JS / Python を表示する。 | | 7. エラーコード体系 | Beta | 原因、対処、リトライ可否、サポートへのエスカレーション経路を備えた安定したエラーカタログ。 | `errors.md` を作り、よくある `invalid_*`、`insufficient_scope`、クォータ、モデレーション、べき等性の失敗すべてからリンクする。 | | 8. マーケットプレイス | Planned | 公開アプリ掲載、開発者プロフィール、アプリインストール、同意前のスコープ表示、レビュー / 評価、掲載取り下げフロー。 | オープンな掲載ではなく、`/dev` からリンクする厳選パートナーアプリのカードから始める。 | | 9. 開発者サポートとチケット | Planned | サポートフォーム、コミュニティディスカッション、課題テンプレート、問い合わせ SLA、セキュリティインシデントのエスカレーション。 | GitHub Discussions、メール / フォーム、必須のデバッグ項目を載せた `/dev/support` またはドキュメントページを追加する。 | | 10. ステータスページと SLA | Planned | 公開ステータス、インシデント履歴、API 可用性目標、Webhook 配信 SLO、メンテナンス告知。 | `/status` を `/dev` からリンクし、開発者向けの API / Webhook のステータス行を追加する。 | | 11. 権限ガバナンス / 管理者認可 | Beta | 組織全体へのインストールに対する管理者同意、高リスクスコープの警告、最小権限のレビュー、監査ログ。 | ポータルに明示的な管理者同意の状態と、高リスクスコープの警告を追加する。 | | 12. エンタープライズのテナント分離と監査 | Beta | 組織 / テナント単位の API キー、ウォレットと支出の管理、監査ログ、SCIM / SSO、データ分離の保証。 | 組織のエージェント / トークンの境界を文書化し、OAuth アプリのイベントについて監査ログのダウンロードを提供する。 | ## すでに稼働しているもの - OAuth 2.0 認可コード + PKCE(`S256` のみ)。 - OIDC のディスカバリー、userinfo、JWKS。 - OAuth 認可サーバーメタデータと保護リソースメタデータ。 - 有効化されている場合、読み取り専用パブリッククライアント向けの動的クライアント登録。 - トークンの失効と、コンフィデンシャルクライアントのトークンイントロスペクション。 - `/openapi.json` の OpenAPI 3.1 と YAML ミラー。 - レシピ / 遺伝子 / 再利用の読み取り API。 - レシピの下書きと公開 API。サンドボックスでの公開ループ用のテストモードつき。 - アプリ登録、スコープ申請、アプリバージョン、使用量 / 呼び出し / アクティビティのログ、シークレットのローテーション履歴。 - Webhook の登録、署名、ping、配信ログ、再配信。 - エンタープライズ的なユースケース向けの組織およびエージェントトークンの機能。 ## GA の受け入れ条件 次がすべて満たされたリリースを GA と呼べます。 1. 新しい開発者が個別のサポートなしで、30 分以内にクイックスタートを完了できる。 2. 最初のトークン、最初のカタログ読み取り、テスト公開、Webhook の ping、エラーの デバッグに、コピー & ペーストできるサンプルがすべて揃っている。 3. ポータルがアプリの状態、申請したスコープ、審査状況、ライブ / テストモード、 直近の呼び出し、クォータ、Webhook の配信失敗、次にとるべきアクションを表示する。 4. OpenAPI、ディスカバリー、ドキュメント、実装が CI で揃った状態を保っている。 5. 少なくとも JS/TS と Python の SDK が存在し、Go も計画済みまたは生成済みである。 6. 高リスクスコープには明示的な審査 / 管理者同意が必要で、監査できる。 7. サポート、ステータス、変更履歴、インシデントの各チャネルが公開され、見つけられる。 8. セキュリティのシグナルが実際に対処可能である。古いクライアントの繰り返しループは 重複排除され、本物のトークン再利用インシデントがノイズに埋もれない。 ## 短期ロードマップ ### P0 —— 面識のない開発者を成功させる - `/dev` を公開の入口として維持する。 - クイックスタートと最小サンプルを仕上げる。 - エラーカタログとトラブルシューティングを追加する。 - ダウンロード可能な Node / Python のサンプルアプリを追加する。 - API エクスプローラーのトークン処理とスニペットを堅牢化する。 ### P1 —— 連携を運用可能にする - Webhook の配信詳細 UI とリプレイ。 - 開発者サポートページと課題テンプレート。 - API / Webhook のステータス行と SLA の記述。 - アプリ審査、スコープ申請、クォータ、Webhook の失敗についてポータルに次アクションの状態を出す。 - リフレッシュトークン失敗時の処理ガイド(リトライループを止め、再ログインを強制する)。 ### P2 —— エコシステムを作る - SDK パッケージ。 - 厳選パートナーアプリ向けのマーケットプレイス初期掲載。 - テナント / 組織のインストールフローと管理者同意。 - 監査エクスポートとエンタープライズのガバナンス機能。 ## 関連ドキュメント - [クイックスタート](./02-quickstart.md) - [最小サンプル](./64-minimal-examples.md) - [API 概要](./40-api-overview.md) - [Webhook](./30-webhooks.md) - [スコープ](./11-scopes.md) - [アプリのバージョン管理](./21-app-versioning.md) - [組織の概要](./50-orgs-overview.md)