[{"data":1,"prerenderedAt":490},["ShallowReactive",2],{"content-/public-gtfs-api-read-only-design-notes":3,"all-pages-for-dir":488,"og-image-/public-gtfs-api-read-only-design-notes":489},{"id":4,"title":5,"body":6,"category":469,"description":470,"extension":471,"meta":472,"navigation":473,"ogImage":474,"path":475,"project_name":476,"published":477,"publishedAt":478,"seo":479,"stem":480,"tags":481,"todo":474,"unpublished":477,"updatedAt":474,"__hash__":487},"pages/2026-06/2026-06-26/public-gtfs-api-read-only-design-notes.md","公開GTFS APIを読み解く — 認証不要で出せる設計のキモはレスポンス本体にattributionを載せること",{"type":7,"value":8,"toc":450},"minimark",[9,13,28,36,41,48,51,57,61,64,75,96,103,107,118,148,155,158,161,166,173,199,206,210,213,238,248,252,255,258,265,283,290,296,299,302,306,309,319,323,330,344,348,351,354,357,371,378,382,385,396,407,414,417,420],[10,11,5],"h1",{"id":12},"公開gtfs-apiを読み解く-認証不要で出せる設計のキモはレスポンス本体にattributionを載せること",[14,15,16,17,21,22,27],"p",{},"朝、",[18,19,20],"code",{},"https://api.transit.ls8h.com/"," というエンドポイントを見つけて「なんで認証不要で公開できているのか？」と気になり、Claude Code に調査を投げた。返ってきた整理が想像以上に綺麗だったので、その場で公開記事にまとめさせた。記事自体は ",[23,24,26],"a",{"href":25},"/transit-ls8h-api-public","認証不要の公共交通 API、api.transit.ls8h.com の正体を読む"," として残してある。",[14,29,30,31,35],{},"この記事はそっちのメタ振り返り。",[32,33,34],"strong",{},"調査の途中でどう詰まったか、ユーザー（筆者本人）の質問で記事がどう厚くなっていったか、最終的に「公開APIの設計」について何を腹落ちできたか","を書く。",[37,38,40],"h2",{"id":39},"出発点-これなんで出せてるの","出発点 — 「これなんで出せてるの？」",[14,42,43,44,47],{},"ブラウザの DevTools で ",[18,45,46],{},"fetch('https://api.transit.ls8h.com/api/v1/feeds')"," がそのまま 200 を返すのを見たときに最初に思ったのが、「これ、出して大丈夫なやつなのか？」だった。乗換案内系のAPIは商用の法人プランしか触ったことがなく、「個人が無料公開」は常識から外れていた。",[14,49,50],{},"Claude Code に投げた問いは1行だけ。",[52,53,54],"blockquote",{},[14,55,56],{},"これってなんでオープンにできてるんですか？認証不要の公開APIで。ちょっとドキュメント漁って整理してください。",[37,58,60],{"id":59},"調査の詰まりポイント-webfetch-が-403-で弾かれた","調査の詰まりポイント — WebFetch が 403 で弾かれた",[14,62,63],{},"最初に WebFetch でトップページを取りに行かせたら、403 で弾かれた。Cloudflare 側の bot 検出が効いていて、AI ユーザーエージェントが弾かれている形。",[14,65,66,67,70,71,74],{},"ここで「アクセスできませんでした」で止まらず、自分のグローバルルール（",[18,68,69],{},"~/.claude/CLAUDE.md","）に書いてある ",[32,72,73],{},"WebFetch 失敗時のフォールバック順序","にそのまま進ませた。",[76,77,78,82,88,93],"ol",{},[79,80,81],"li",{},"WebFetch（403 で死亡）",[79,83,84,87],{},[18,85,86],{},"r.jina.ai"," プロキシ経由でフルレンダリング → Markdown 取得（成功）",[79,89,90,92],{},[18,91,86],{}," 経由で OpenAPI JSON も直接取得（成功）",[79,94,95],{},"最後に ls8h.com 本体だけ Jina 経由で確認",[14,97,98,99,102],{},"この順序を CLAUDE.md に書いておいた価値が出た瞬間だった。",[32,100,101],{},"「失敗したら次の手」をルールで固定しておくと、AIに「ダメでした」で止められない","。",[37,104,106],{"id":105},"_1回目の結論-4つの設計判断","1回目の結論 — 4つの設計判断",[14,108,109,110,113,114,117],{},"OpenAPI と ",[18,111,112],{},"/feeds"," ",[18,115,116],{},"/operators"," を読んだあと、Claude Code が出してきた結論は次の4点だった。",[76,119,120,126,132,138],{},[79,121,122,125],{},[32,123,124],{},"GET のみ・書き込み一切なし"," — ユーザー単位の状態を持たなくていい",[79,127,128,131],{},[32,129,130],{},"配信元は公的 GTFS オープンデータ"," — 一次データを囲い込む法的・経済的理由がない",[79,133,134,137],{},[32,135,136],{},"ライセンス表記は feed / operator 単位でレスポンスに同梱"," — クレジット表示義務を利用者に物理的に手渡す",[79,139,140,147],{},[32,141,142,143,146],{},"CORS ",[18,144,145],{},"*"," でブラウザから直接叩かせる設計"," — サーバー側プロキシ不要",[14,149,150,151,154],{},"これを聞いて自分の中で形が見えた瞬間、ふと口に出たのが「",[32,152,153],{},"素材としてのオープンデータ（GTFS ZIP の山）を、経路検索できるAPIに化けさせた統合レイヤー","」というフレーズだった。Claude Code に「その理解で正解です」と確認してもらって、この一行を記事の核に据えた。",[37,156,157],{"id":157},"ユーザーの追い質問が記事を厚くした",[14,159,160],{},"ここからが面白かった。最初の結論で記事をまとめさせたあと、自分の中で2つ詰めきれない点が残っていて、立て続けに追加質問を投げた。",[162,163,165],"h3",{"id":164},"質問1-レスポンス本体に-attribution-を載せて何がどう解決されてるんですか","質問1: 「レスポンス本体に attribution を載せて、何がどう解決されてるんですか？」",[14,167,168,169,172],{},"最初の記事では「ライセンス表記が同梱されている」とだけ書かれていて、",[32,170,171],{},"それで何が嬉しいのか","が抜けていた。質問を投げたら、Claude Code が4点に分解して返してきた。",[76,174,175,181,187,193],{},[79,176,177,180],{},[32,178,179],{},"クレジット表示義務の「伝達」が成立する"," — README に書くのと違って、レスポンスに乗せておけばコードから取り出せる",[79,182,183,186],{},[32,184,185],{},"利用者がライセンス文言をハードコードしなくていい"," — 100社ぶんの attribution テーブルを利用者側で抱える必要がない",[79,188,189,192],{},[32,190,191],{},"フィード追加・ライセンス改定が自動追従する"," — 利用者側のリビルドなしで反映される",[79,194,195,198],{},[32,196,197],{},"「API 提供者がライセンス遵守の責任を果たした」状態を作れる"," — 火の粉が再配信者に飛んでくる可能性を設計上下げられる",[14,200,201,202,205],{},"ここを読んで腹落ちした。「attribution をレスポンスに埋める」は、単なる便利機能じゃなくて、",[32,203,204],{},"ライセンス遵守の作業を利用者と API 提供者で分担できる構造を持っている","ということだった。フィードごとに異なるライセンス条件（CC BY、ODbL、各社独自）という、本来なら配信者を窒息させかねない問題を、構造で解いている。",[162,207,209],{"id":208},"質問2-これ出してる人の目的って何なんですかね","質問2: 「これ出してる人の目的って何なんですかね」",[14,211,212],{},"これは個人プロジェクトの動機を探る雑談だったけど、答えが面白かった。",[14,214,215,218,219,222,223,226,227,226,230,233,234,237],{},[18,216,217],{},"/api/v1/map/3d-scene"," と、",[18,220,221],{},"/api/v1/guidance/plan"," の ",[18,224,225],{},"live",", ",[18,228,229],{},"tracking",[18,231,232],{},"strategy"," パラメータがある。これは",[32,235,236],{},"ナビゲーションUIを持つフロントエンドを作る前提","でないと出てこない設計。つまり「自分が使いたいクライアントアプリの裏側として作って、ついでに認証なしで叩けるようにした」というのが一番自然な読み方だった。",[14,239,240,243,244,247],{},[18,241,242],{},"ls8h.com"," のトップに並んでいるアイコンジェネレータ、麻雀牌エンコードの雰囲気とも整合する。商売じゃなく",[32,245,246],{},"自分の遊び場","として置いてある。",[162,249,251],{"id":250},"質問3-他の人が使う場合の料金ってかかんないんでしたっけオープンにする方が実装は楽なんでしたっけ","質問3: 「他の人が使う場合の料金ってかかんないんでしたっけ？オープンにする方が実装は楽なんでしたっけ？」",[14,253,254],{},"ここで一番大きな腹落ちがあった。",[14,256,257],{},"運用コストの試算をさせると、月数千円〜数万円のインフラ代で回せる規模。GTFS 全国分でも数十 GB、Raptor や CSA でオンメモリ検索なら VPS 1〜2 台で完結する。",[14,259,260,261,264],{},"そして「オープンの方が実装が楽」という直感に反する話。クローズドにすると次のものが",[32,262,263],{},"全部必要","になる。",[266,267,268,271,274,277,280],"ul",{},[79,269,270],{},"認証（ユーザーDB、サインアップUI、メール認証、APIキー発行）",[79,272,273],{},"課金（Stripe連携、サブスク管理、請求書、税務）",[79,275,276],{},"法務（利用規約、プライバシーポリシー、特定商取引法表記）",[79,278,279],{},"サポート（ログイン窓口、請求窓口、APIキー漏洩対応）",[79,281,282],{},"監視（ユーザー単位の使用量、不正検知）",[14,284,285,286,289],{},"これがAPIコアの実装と",[32,287,288],{},"同じかそれ以上の重さ","になる。しかも本業の経路検索の改善には1ミリも寄与しない。",[14,291,292,295],{},[32,293,294],{},"「作りたかったのは経路検索エンジンであって認証システムじゃない」"," という個人開発者の本音が、設計に直接出ている。",[37,297,298],{"id":298},"学びの箇所",[14,300,301],{},"書き終わったあとに残った、自分の頭の整理。",[162,303,305],{"id":304},"_1-公開api危険ではない","1. 公開API＝危険、ではない",[14,307,308],{},"ずっと「APIをオープンにする＝認証付きAPIより危険」と漠然と思っていた。今日の調査で逆転した。",[14,310,311,314,315,318],{},[32,312,313],{},"読み取り専用（GET only）＋ライセンスをレスポンス同梱","という2点を満たせば、",[32,316,317],{},"設計レベルで安全に公開できる","。書き込みがなければユーザー単位の状態を持たなくていいし、ライセンス義務はレスポンスで伝達されるので「表記漏れの責任」を利用者に渡せる。",[162,320,322],{"id":321},"_2-フィードごとに異なるライセンス条件はレスポンス同梱で構造的に解ける","2. フィードごとに異なるライセンス条件は「レスポンス同梱」で構造的に解ける",[14,324,325,326,329],{},"GTFS フィードのライセンスはバラバラ。CC BY、ODbL、各社独自規約が混ざる。「READMEに全部書く」「利用者がハードコードする」だと",[32,327,328],{},"フィード追加のたびに利用者全員が手を入れる","羽目になる。",[14,331,332,335,336,339,340,343],{},[18,333,334],{},"/api/v1/feeds"," で attribution を返す設計だと、",[32,337,338],{},"フィードが増えても利用者側は何もしなくていい","。これは API のメンテナビリティの話というより、",[32,341,342],{},"ライセンス遵守という義務の所在を構造的に明確にする","話だった。",[162,345,347],{"id":346},"_3-オープンの方が個人開発者にとって運用が楽","3. オープンの方が個人開発者にとって運用が楽",[14,349,350],{},"今までクローズドの方が「ちゃんとしてる」感があると思っていたが、実態は逆。",[14,352,353],{},"オープンにすると認証・課金・法務・サポートが全部消える。代わりに必要なのは「無保証で出してます」の一行と、自腹でインフラ代を負担する覚悟だけ。",[14,355,356],{},"ただし「楽さ」を享受できるのは次の4条件が揃ったとき。",[76,358,359,362,365,368],{},[79,360,361],{},"一次データがオープンデータ",[79,363,364],{},"読み取り専用",[79,366,367],{},"個人プロジェクト（収益化義務なし）",[79,369,370],{},"attribution 設計でライセンス義務を利用者に渡せる",[14,372,373,374,377],{},"1つでも欠けると話は変わる。",[32,375,376],{},"「オープンが楽」は設計条件の上に成立する性質","であって、無条件には成立しない。",[37,379,381],{"id":380},"メタな学び-ユーザーの追い質問が記事を完成させた","メタな学び — 「ユーザーの追い質問が記事を完成させた」",[14,383,384],{},"最初の Claude Code の整理（4点の結論）は、それだけ読むと「綺麗にまとまっている」けど、自分の中に残る違和感は埋まっていなかった。",[266,386,387,390,393],{},[79,388,389],{},"「attribution が載っている」← で何が嬉しいの？",[79,391,392],{},"「個人プロジェクト」← なんで出してるの？",[79,394,395],{},"「認証不要」← オープンにする方が本当に楽なの？",[14,397,398,399,402,403,406],{},"この3つの違和感を順番に質問として投げたから、最終的な公開記事は ",[32,400,401],{},"「設計判断 → なぜそうしたか → 何が解決されているか → 個人開発者の経済合理性」"," まで含む厚みになった。AI に最初の整理を出させたあと、",[32,404,405],{},"自分の中の違和感を質問の形で投げ続ける","作業を5往復ぐらいやった結果が、いまの公開記事になっている。",[14,408,409,410,413],{},"人間が判断する係、AI が実行・整理する係という構図がよく回った1本だった。整理力は AI に任せるとして、",[32,411,412],{},"「何が腹落ちしていないか」を言語化するのは人間側の仕事","だ、というのを改めて確認した。",[415,416],"hr",{},[14,418,419],{},"参考:",[266,421,422,427,436,443],{},[79,423,424,425],{},"本体記事: ",[23,426,26],{"href":25},[79,428,429],{},[23,430,435],{"href":20,"target":431,"rel":432},"_blank",[433,434],"noopener","noreferrer","Transit API トップ",[79,437,438],{},[23,439,442],{"href":440,"target":431,"rel":441},"https://api.transit.ls8h.com/api/openapi.json",[433,434],"OpenAPI 仕様 JSON",[79,444,445],{},[23,446,449],{"href":447,"target":431,"rel":448},"https://ls8h.com/",[433,434],"ls8h.com（運営者ポートフォリオ）",{"title":451,"searchDepth":452,"depth":452,"links":453},"",2,[454,455,456,457,463,468],{"id":39,"depth":452,"text":40},{"id":59,"depth":452,"text":60},{"id":105,"depth":452,"text":106},{"id":157,"depth":452,"text":157,"children":458},[459,461,462],{"id":164,"depth":460,"text":165},3,{"id":208,"depth":460,"text":209},{"id":250,"depth":460,"text":251},{"id":298,"depth":452,"text":298,"children":464},[465,466,467],{"id":304,"depth":460,"text":305},{"id":321,"depth":460,"text":322},{"id":346,"depth":460,"text":347},{"id":380,"depth":452,"text":381},"dev","個人開発者が出している認証不要の経路検索API（api.transit.ls8h.com）の設計を、ユーザーの質問に押されながら半日かけて読み解いた振り返り。WebFetchが403で弾かれてr.jina.ai経由に切り替えた話から、なぜオープンが楽になるかの腹落ちまでメモした。","md",{},true,null,"/public-gtfs-api-read-only-design-notes","misc-dev",false,"2026-06-26T00:00:00.000Z",{"title":5,"description":470},"2026-06/2026-06-26/public-gtfs-api-read-only-design-notes",[482,483,484,485,486],"GTFS","公開API","オープンデータ","個人開発","attribution","7fLL0T2B1K88WzNKtH6iVFDO4td5lP6MG4-9WXcZO-k",[],"https://log.eurekapu.com/og/blog/public-gtfs-api-read-only-design-notes.png?v=2026-06-26T00%3A00%3A00.000Z&title=%E5%85%AC%E9%96%8BGTFS%20API%E3%82%92%E8%AA%AD%E3%81%BF%E8%A7%A3%E3%81%8F%20%E2%80%94%20%E8%AA%8D%E8%A8%BC%E4%B8%8D%E8%A6%81%E3%81%A7%E5%87%BA%E3%81%9B%E3%82%8B%E8%A8%AD%E8%A8%88%E3%81%AE%E3%82%AD%E3%83%A2%E3%81%AF%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E6%9C%AC%E4%BD%93%E3%81%ABattribution%E3%82%92%E8%BC%89%E3%81%9B%E3%82%8B%E3%81%93%E3%81%A8&author=Kei%20Komatsu&sig=040afd4a2a89c4c6",1782528861092]