[{"data":1,"prerenderedAt":684},["ShallowReactive",2],{"content-/browser-to-api-explainer":3,"all-pages-for-dir":661,"related-/browser-to-api-explainer":662,"og-image-/browser-to-api-explainer":683},{"id":4,"title":5,"body":6,"category":642,"concepts":643,"description":644,"extension":645,"meta":646,"navigation":647,"ogImage":643,"path":648,"project_name":643,"published":649,"publishedAt":650,"seo":651,"source":643,"stem":652,"tags":653,"todo":643,"unpublished":649,"updatedAt":643,"__hash__":660},"pages/2026-05/2026-05-18/browser-to-api-explainer.md","browserbase/skills の browser-to-api を読んだ ── ブラウザを踏ませて OpenAPI 3.1 を自動生成するスキル",{"type":7,"value":8,"toc":628},"minimark",[9,13,44,52,56,72,82,111,115,133,241,253,256,261,306,330,334,337,378,401,405,437,440,443,449,501,507,510,544,547,567,570,573,580,589,592,595,598],[10,11,12],"h2",{"id":12},"きっかけ",[14,15,16,17,21,22,25,26,29,30,37,38,43],"p",{},"朝、X で ",[18,19,20],"code",{},"browserbase/skills"," というリポジトリが流れてきた。Claude Code 向けにブラウザ自動化と Browserbase 連携をひとまとめにした公式スキル集で、その中に ",[18,23,24],{},"browser-to-api"," という気になる名前のフォルダがあった。",[18,27,28],{},"--skill browser-to-api"," でユーザーが指してきたので、何をするスキルなのかを ",[31,32,36],"a",{"href":33,"rel":34},"https://github.com/browserbase/skills/blob/main/skills/browser-to-api/SKILL.md",[35],"nofollow","SKILL.md"," と ",[31,39,42],{"href":40,"rel":41},"https://github.com/browserbase/skills/blob/main/skills/browser-to-api/REFERENCE.md",[35],"REFERENCE.md"," を読みながら整理した。",[14,45,46,47,51],{},"結論を先に書くと、これは「",[48,49,50],"strong",{},"ブラウザを操作してサイトを踏み回らせると、その背後で動いている内部 API の OpenAPI 3.1 スペックが裏で勝手に組み上がる","」というスキルだった。サイトが OpenAPI を公開していようがいまいが関係ない。観察できる HTTP トラフィックから帰納的に仕様を再構成する。",[10,53,55],{"id":54},"スキルの立ち位置-踏む人と読む人の分業","スキルの立ち位置 ── 「踏む人」と「読む人」の分業",[14,57,58,60,61,64,65,68,69,71],{},[18,59,24],{}," 単体ではトラフィックをキャプチャしない。キャプチャは姉妹スキルの ",[18,62,63],{},"browser-trace"," の仕事で、こちらは Chrome DevTools Protocol（CDP）のイベントを全部 ",[18,66,67],{},".jsonl"," に書き出すロガーになっている。",[18,70,24],{}," はその出力ディレクトリを入力として読み、後処理で OpenAPI を吐く。",[73,74,79],"pre",{"className":75,"code":77,"language":78},[76],"language-text","browser-trace    →  .o11y/\u003Crun>/cdp/network/{requests,responses}.jsonl\nbrowser-to-api   →  .o11y/\u003Crun>/api-spec/index.html + openapi.yaml + client.mjs\n","text",[18,80,77],{"__ignoreMap":81},"",[14,83,84,85,88,89,37,92,95,96,98,99,102,103,106,107,110],{},"CDP のリクエスト/レスポンスを ",[18,86,87],{},"requestId"," でペアリングして、URL をテンプレート化し、JSON ボディからスキーマを推論し、",[18,90,91],{},"paths",[18,93,94],{},"components.schemas"," を組み立てる。",[18,97,63],{}," 側で ",[18,100,101],{},"browse network on"," を併用するとレスポンスボディも join できて、レスポンス側のスキーマまで埋まる。CDP の firehose 単体だとリクエストボディ（",[18,104,105],{},"postData","）は取れるが、レスポンスボディは別途 ",[18,108,109],{},"Network.getResponseBody"," で取りに行かないと埋まらない、という Chrome DevTools の仕様の隙間を回避する形になっている。",[10,112,114],{"id":113},"パイプラインは-5-段の関数合成","パイプラインは 5 段の関数合成",[14,116,117,120,121,124,125,128,129,132],{},[18,118,119],{},"discover.mjs"," という top-level ディスパッチャがあって、その下で ",[18,122,123],{},"load → filter → normalize → infer → emit"," が順に走る。それぞれ 1 ファイル 1 ステージで、",[18,126,127],{},"--stage \u003Cname>"," で部分的に再実行できる。Node 18+ の標準ライブラリだけで動き、",[18,130,131],{},"npm install"," も要らない。",[134,135,136,149],"table",{},[137,138,139],"thead",{},[140,141,142,146],"tr",{},[143,144,145],"th",{},"ステージ",[143,147,148],{},"やること",[150,151,152,166,186,208,218],"tbody",{},[140,153,154,160],{},[155,156,157],"td",{},[18,158,159],{},"load",[155,161,162,163,165],{},"requests.jsonl と responses.jsonl を ",[18,164,87],{}," でペアリング。OPTIONS や redirect は捨てる。Image/CSS/Font 等の非API リソースも捨てる。body ディレクトリがあれば join",[140,167,168,173],{},[155,169,170],{},[18,171,172],{},"filter",[155,174,175,178,179,178,182,185],{},[18,176,177],{},"--include"," / ",[18,180,181],{},"--exclude",[18,183,184],{},"--origins"," を適用。組み込みの除外リスト（analytics、sourcemap、service worker、フォント）も適用",[140,187,188,193],{},[155,189,190],{},[18,191,192],{},"normalize",[155,194,195,196,199,200,203,204,207],{},"URL のパスをテンプレート化（",[18,197,198],{},"/items/42"," → ",[18,201,202],{},"/items/{id}","）。",[18,205,206],{},"(origin, method, templatedPath)"," でグルーピング",[140,209,210,215],{},[155,211,212],{},[18,213,214],{},"infer",[155,216,217],{},"リクエスト・レスポンスのボディから JSON スキーマを帰納推論。required / enum / format を判定",[140,219,220,225],{},[155,221,222],{},[18,223,224],{},"emit",[155,226,227,228,230,231,37,234,37,237,240],{},"OpenAPI 3.1 を YAML/JSON で出力。同じ構造のスキーマは ",[18,229,94],{}," にホイスト。",[18,232,233],{},"report.md",[18,235,236],{},"client.mjs",[18,238,239],{},"index.html"," も生成",[14,242,243,244,246,247,249,250,252],{},"最終成果物の ",[18,245,239],{}," はサーバー不要の自己完結 HTML レポートで、",[18,248,24],{}," のドキュメントが「常にこれを開け」と言っている。各オペレーションを展開可能なカードで並べ、変数表・curl 例・クライアント呼び出しコード・リクエスト/レスポンス例を全部入れてくれる。",[18,251,236],{}," は zero-dep の fetch ラッパで、各エンドポイントにつき関数が 1 個生える。",[10,254,255],{"id":255},"パステンプレートの判定ルールが意外と細かい",[14,257,258,260],{},[18,259,192],{}," ステージの中身を REFERENCE.md で確認すると、URL パスのテンプレート化は以下の優先順位で動く。",[262,263,264,280,288,295,303],"ol",{},[265,266,267,268,271,272,275,276,279],"li",{},"UUID v1〜v5 → ",[18,269,270],{},"{id}","（",[18,273,274],{},"string",", ",[18,277,278],{},"format: uuid","）",[265,281,282,283,271,285,279],{},"純粋な整数 → ",[18,284,270],{},[18,286,287],{},"integer",[265,289,290,291,271,293,279],{},"Hex / base62 で 8 文字以上 → ",[18,292,270],{},[18,294,274],{},[265,296,297,298,271,301,279],{},"同位置の短い alpha トークンが複数サンプルで揺れる → ",[18,299,300],{},"{slug}",[18,302,274],{},[265,304,305],{},"それ以外は静的セグメントとして残す",[14,307,308,309,275,311,275,314,317,318,321,322,325,326,329],{},"複数の変数セグメントが同じパスにあると ",[18,310,270],{},[18,312,313],{},"{id2}",[18,315,316],{},"{id3}"," と添字が付く。ここで気になったのは、テンプレート化前のパスは異なるのに正規化後に同じテンプレートに collapse する組が、レスポンスの形（status / content-type）が違う場合は ",[48,319,320],{},"意図的に分けたまま残してフラグを立てる","ことだ。",[18,323,324],{},"divergent-response-shape"," という normalization フラグが付いて ",[18,327,328],{},"confidence.json"," に記録される。「とりあえず潰す」ではなく「分けるべきものは分かる範囲で分ける」という方針が、過正規化対策として効いている。",[10,331,333],{"id":332},"スキーマ推論は帰納で契約じゃない","スキーマ推論は帰納で、契約じゃない",[14,335,336],{},"JSON ボディからの型推論は次のルールで動く。",[338,339,340,346,352,358],"ul",{},[265,341,342,345],{},[18,343,344],{},"required",": すべてのサンプルに存在したフィールドだけ",[265,347,348,351],{},[18,349,350],{},"type",": 観察された型のユニオン",[265,353,354,357],{},[18,355,356],{},"enum",": 異なる値が 8 個以下、かつサンプル数 5 件以上のとき",[265,359,360,363,364,367,368,178,371,178,374,377],{},[18,361,362],{},"format",": ",[18,365,366],{},"date-time","（ISO っぽい文字列）/ ",[18,369,370],{},"uri",[18,372,373],{},"email",[18,375,376],{},"uuid"," を検出",[14,379,380,381,383,384,178,387,178,390,393,394,37,397,400],{},"ドキュメント自身が「これは inductive であって contractual ではない」と明示している。「全サンプルに含まれていたから required にしたが、サーバー側では optional かもしれない」「観察した範囲では enum に見えるが、本当はオープンセットかもしれない」というのを正直に書いている。",[18,382,328],{}," でエンドポイントごとの信頼度を ",[18,385,386],{},"low",[18,388,389],{},"medium",[18,391,392],{},"high"," の 3 段で開示し、",[18,395,396],{},"x-confidence",[18,398,399],{},"x-sample-count"," を OpenAPI 拡張として埋め込む。下流のクライアントジェネレータや SDK ビルダーが「サンプル少ないからこの型は弱い」と判断できるように設計されている。",[10,402,404],{"id":403},"graphql-や-json-rpc-のような多重化エンドポイントを自動分解","GraphQL や JSON-RPC のような多重化エンドポイントを自動分解",[14,406,407,408,411,412,415,416,178,419,178,422,425,426,178,429,432,433,436],{},"これは便利だなと思った機能。",[18,409,410],{},"/graphql"," や ",[18,413,414],{},"/dapi/fe/gql"," のように 1 つの URL で多種のオペレーションを多重化するエンドポイントを、リクエストボディの ",[18,417,418],{},"operationName",[18,420,421],{},"method",[18,423,424],{},"action"," などのフィールド、あるいはクエリパラメータの ",[18,427,428],{},"opname",[18,430,431],{},"op"," を見て、論理オペレーションごとに分解する。OpenAPI 上は ",[18,434,435],{},"/dapi/fe/gql [Autocomplete]"," のようにラベル付きの別パスとして並ぶ。GraphQL APQ、JSON-RPC、似た形のディスパッチパターン全般がカバーされる。",[14,438,439],{},"「1 エンドポイント = 1 オペレーション」の前提が崩れる現代的な API でも、観察ベースで分けてくれるのは現実的だ。",[10,441,442],{"id":442},"ノイズフィルタが強い",[14,444,445,446,448],{},"normalize 段で自動的に捨てられるトラフィックの分類が ",[18,447,36],{}," に書いてある。",[338,450,451,469,480,490],{},[265,452,453,454,275,457,275,460,275,463,275,466],{},"トラッキング / アナリティクス: ",[18,455,456],{},"/track",[18,458,459],{},"/pixel",[18,461,462],{},"/beacon",[18,464,465],{},"/impression",[18,467,468],{},"/pageview",[265,470,471,472,475,476,479],{},"Bot 防御: Akamai（",[18,473,474],{},"/akam/","）, fingerprint 系（",[18,477,478],{},"sensor_data","）, 難読化された多段パス",[265,481,482,483,275,486,489],{},"セッション系: ",[18,484,485],{},"/session",[18,487,488],{},"/authenticate/start",", cookie consent, A/B 実験",[265,491,492,493,496,497,500],{},"ページレンダリング自体: ",[18,494,495],{},"GET"," で ",[18,498,499],{},"text/html"," を返すもの",[14,502,503,504,506],{},"これだけで普通のサイトのトラフィックの 60〜80% が落ちる、と書いてあった。実感としては妥当な数字に見える。漁師の魚拓を取るときに、海水ごと展示しても仕方ないのと同じで、欲しいのは魚＝API コール部分だけだ。",[18,505,177],{}," で例外を救う設計も入っている。",[10,508,509],{"id":509},"レダクションも標準で入っている",[14,511,512,178,515,178,518,178,521,178,524,527,528,531,532,535,536,539,540,543],{},[18,513,514],{},"authorization",[18,516,517],{},"cookie",[18,519,520],{},"x-api-key",[18,522,523],{},"*token*",[18,525,526],{},"*secret*"," のヘッダ、JWT 形式の文字列、メールアドレス、E.164 電話番号は ",[18,529,530],{},"\"\u003Credacted>\""," に置換される。型は保持されるのでスキーマ推論には影響しない。",[18,533,534],{},"--redact"," で追加もできる。Auth スキーム自体は OpenAPI の ",[18,537,538],{},"security"," には書かず、",[18,541,542],{},"x-observed-auth"," という拡張に観察値を記録するだけにとどめている。「観察したものは観察したと書く、推測はしない」というスタンスが徹底している。",[10,545,546],{"id":546},"制約も正直に書いてある",[338,548,549,552,555,558,564],{},[265,550,551],{},"覆える範囲はキャプチャで踏んだフローだけ。踏まなかったエンドポイントは存在しないことになる",[265,553,554],{},"スキーマは帰納的なので、すべてのサンプルに含まれていたフィールドでもサーバー側では optional かもしれない",[265,556,557],{},"Auth は観察するだけで、セキュリティスキームを宣言はしない",[265,559,560,561,563],{},"パステンプレートはヒューリスティック。曖昧な URL は ",[18,562,328],{}," でフラグが立つ",[265,565,566],{},"レダクションは best-effort。アプリ固有のシークレットは漏れる余地が残る",[14,568,569],{},"このあたりを「完成度」ではなく「観察できた範囲の写し絵」として開示しているのが、ドキュメントとして信頼できる感触に繋がっていた。",[10,571,572],{"id":572},"読み終えての感想",[14,574,575,576,579],{},"OpenAPI を「先に書くもの」から「",[48,577,578],{},"ブラウザを踏ませた後に勝手に出てくるもの","」に位置付けを変えるスキルだなと感じた。手動で書いた仕様書よりも、実際のトラフィックから組み上がったスペックの方が、その瞬間の現実に対しては忠実だ。サーバー側のコードを書いた本人がドキュメントを書き忘れていても、クライアント側で踏めば勝手にそれが補える。",[14,581,582,583,585,586,588],{},"業務寄りで考えると、関与先で使っている SaaS の内部 API が変わったことを早期に検知したいときに、定期的に ",[18,584,63],{}," で踏ませて ",[18,587,24],{}," で吐かせて、前回スペックとの diff を取る、というモニタリング用途がそのまま使える。サーバー側に Swagger が無くても自前で起こせる。",[14,590,591],{},"ただし、踏んだフローでしかカバーできない、というのはずっと残る制約で、これは A/B テストでホールドアウトされてしまった分岐や、エラーパスの観察漏れを意味する。「本物のスペック」ではなく「観察できた範囲のスペック」、という前提を忘れずに使いたい。",[14,593,594],{},"利用規約との折り合いについては、踏むサイトごとに個別に判断する話なので、スキル側で吸収できる類のリスクではない。ここは別途整理する必要があると感じた。",[10,596,597],{"id":597},"参考リンク",[338,599,600,607,616,625],{},[265,601,602,603],{},"リポジトリ: ",[31,604,20],{"href":605,"rel":606},"https://github.com/browserbase/skills",[35],[265,608,609,610,178,613],{},"該当スキル: ",[31,611,36],{"href":33,"rel":612},[35],[31,614,42],{"href":40,"rel":615},[35],[265,617,618,619,624],{},"姉妹スキル: ",[31,620,623],{"href":621,"rel":622},"https://github.com/browserbase/skills/tree/main/skills/browser-trace",[35],"skills/browser-trace","（CDP キャプチャ側）",[265,626,627],{},"ライセンス: MIT",{"title":81,"searchDepth":629,"depth":629,"links":630},2,[631,632,633,634,635,636,637,638,639,640,641],{"id":12,"depth":629,"text":12},{"id":54,"depth":629,"text":55},{"id":113,"depth":629,"text":114},{"id":255,"depth":629,"text":255},{"id":332,"depth":629,"text":333},{"id":403,"depth":629,"text":404},{"id":442,"depth":629,"text":442},{"id":509,"depth":629,"text":509},{"id":546,"depth":629,"text":546},{"id":572,"depth":629,"text":572},{"id":597,"depth":629,"text":597},"dev",null,"browserbase/skills が公開している browser-to-api スキルが、browser-trace でキャプチャした CDP トラフィックをオフラインで解析し、OpenAPI 3.1 スペックと fetch クライアントを自動生成する仕組みを SKILL.md と REFERENCE.md から読み解いた記録","md",{},true,"/browser-to-api-explainer",false,"2026-05-18T00:00:00.000Z",{"title":5,"description":644},"2026-05/2026-05-18/browser-to-api-explainer",[654,655,656,657,658,659],"browserbase","openapi","chrome-devtools-protocol","claude-code","skill","api-discovery","K5TIkOL2p48xvYb0e7mv6M-bN_s9LqLFC3FYEUaeaN4",[],[663,667,671,675,679],{"title":664,"path":665,"publishedAt":666},"PPT Masterを読んだので、SVG配布スキルに移植する部分だけ拾った","/ppt-master-extract-svg-parts","2026-07-01T00:00:00.000Z",{"title":668,"path":669,"publishedAt":670},"蔵書DBの専門書からClaude Codeスキルを自動生成する — /book-to-skillワークフロー実践記","/book-to-skill-document-writing","2026-06-10T00:00:00.000Z",{"title":672,"path":673,"publishedAt":674},"Excel風HTMLモックの数式吹き出しヘルパーをClaude Codeのプロジェクトスキル化して11関数の解説に適用した記録","/excel-html-mock-skill-and-functions","2026-05-04T00:00:00.000Z",{"title":676,"path":677,"publishedAt":678},"コードと画面の対応をスライドで追える feature-slides スキルを作った","/feature-slides-skill","2026-04-28T00:00:00.000Z",{"title":680,"path":681,"publishedAt":682},"OCR化した書籍からClaude Codeスキルを自動生成するスラッシュコマンドを作成","/book-to-skill-command","2026-01-06T00:00:00.000Z","https://log.eurekapu.com/og/blog/browser-to-api-explainer.png?v=2026-05-18T00%3A00%3A00.000Z&title=browserbase%2Fskills%20%E3%81%AE%20browser-to-api%20%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%20%E2%94%80%E2%94%80%20%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%82%92%E8%B8%8F%E3%81%BE%E3%81%9B%E3%81%A6%20OpenAPI%203.1%20%E3%82%92%E8%87%AA%E5%8B%95%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E3%82%B9%E3%82%AD%E3%83%AB&author=Kei%20Komatsu&sig=b330b42fc0ce4e75",1785654992386]