未分類

認証不要の公共交通 API、api.transit.ls8h.com の正体を読む

https://api.transit.ls8h.com/ というエンドポイントを見つけた。日本の電車・バスの経路検索が API キーなし・CORS 全開で叩ける。ブラウザの DevTools で fetch() を叩けば、そのまま結果が返ってくる。

「乗換案内系の API は法人契約しないと触れない」というのが常識だった身からすると、まず「これは出して大丈夫なやつなのか?」が気になった。OpenAPI 仕様と /feeds /operators を読んだ結論を先に書くと、これはオープンデータの再配信ではなく、バラバラの GTFS フィードを集めて経路検索エンジンを乗せた統合レイヤーだった。

何ができる API か

OpenAPI(https://api.transit.ls8h.com/api/openapi.json)から拾えるエンドポイントはこれだけ。

MethodPath用途
GET/api/healthLiveness probe
GET/api/v1/feeds取り込み済みフィード一覧(attribution つき)
GET/api/v1/operators事業者ブランディング・ロゴ・ライセンス
GET/api/v1/locations/suggest駅オートコンプリート
GET/api/v1/places/suggest地点オートコンプリート
GET/api/v1/places/reverse逆ジオコーディング
GET/api/v1/stations/{id}駅詳細(ホーム・路線)
GET/api/v1/stations/{id}/departures出発時刻表
GET/api/v1/plan経路検索(出発・到着・始発・終電)
GET/api/v1/guidance/planランキング付きの案内プラン
GET/api/v1/map/3d-scene3D ビルディングシーン

全部 GET で書き込みなし。securitySchemes は OpenAPI に定義されていないAccess-Control-Allow-Origin: * で、ブラウザから直接叩く前提の設計になっている。

ID と時刻の表現にクセがあって、

  • 駅・停留所は feedId:stopId のフィード修飾子つき
  • 地点は geo:<lat>,<lon>
  • 時刻はサービス日の 0:00 からの秒数で、86400 を超える値(翌日扱い)や負の値(前日扱い)も返ってくる

ここを知らずに new Date() に放り込むとズレる。

なぜ認証不要で出せるのか

「経路検索 API を無料公開」と聞くと、駅すぱあと API や駅探の法人プランが頭に浮かぶ。あれと違って認証不要で出せている理由は、API そのものが持っているデータの素性が違うから。

/api/v1/feeds を叩くと、各フィードに feedId / name / attribution / downloadUrl / catalog が入っていて、catalog は次のいずれか。

  • manual — 公式 PDF・サイトから手動取り込み
  • gtfs-data.jpGTFS-Data.jp 経由
  • hoda-ckan — 北海道オープンデータ
  • mobility-databaseMobility Database
  • scrape — Web スクレイピング由来

つまり一次データはすべてオープンデータ。これが認証不要で出せている前提条件になっている。

ライセンス表記を「レスポンス本体」に埋めるという設計

ここから先がこの API のいちばん巧いところで、もうひとつ書いておきたい。

GTFS フィードのライセンス(CC BY、ODbL、各社独自規約)はだいたい 「最終ユーザーが見える場所にクレジットを出せ」 という条件を含んでいる。なので「オープンデータだから自由に再配信していい」とは単純には言えない。再配信者である ls8h.com には、そのクレジット表記義務を最終利用者(API を叩く側)まで運ぶ責任がついて回る。

しかも、/api/v1/feeds から見ても分かるとおりフィードごとにライセンス条件が違う。「ひとつのライセンス文言を README に書いておけば終わり」では済まず、フィード単位で attribution 文と参照 URL が変わる。これをどう伝達するかが設計上の論点になる。

ls8h.com の解は、API のレスポンス本体に attribution を同梱して返す こと。/api/v1/feeds /api/v1/operators を叩けば、いま自分が使っているデータの帰属表記が一覧で取れる。これで何が片付くかというと、

1. クレジット表示義務の「伝達」が成立する

README に書いておくのと違って、レスポンスに乗せておけばコードから取り出せる。利用者が無視したらそれは利用者の責任、という状態を API の構造として作れる。「ライセンスに従えと書いてはあったが、どう従えばいいか伝達されていなかった」という言い訳が成立しなくなる、と言ってもいい。

2. 利用者がライセンス文言をハードコードしなくていい

普通、外部データのクレジット表示を実装するアプリは

// 利用者側がコピペで持つことになる
const ATTRIBUTIONS = {
  'jr-east': '東日本旅客鉄道株式会社 ...',
  'odakyu':  '小田急電鉄 ...',
  // ...100社ぶん
}

を抱える羽目になる。フィード一覧と表記文を利用者側でメンテし続けないといけない。API がレスポンスに attribution を返してくれれば、

const credits = await (await fetch('/api/v1/feeds')).json()
return credits.map(c => `${c.name}: ${c.attribution}`)

で終わる。事業者一覧と表記文の管理を完全に API 側に寄せられる。

3. フィード追加・ライセンス改定が自動追従する

ls8h.com が新しい鉄道会社の GTFS を取り込んだら、/api/v1/feeds のレスポンスに自動で増える。利用者は何もしなくても 新フィードのクレジットが UI に反映される

逆に、あるフィードのライセンス文言が更新されたとき(表記が変わった、参照 URL が変わった)も、利用者側のリビルドなしに追従できる。これは「ハードコード」では絶対に成立しない性質。

4. 「API 提供者がライセンス遵守の責任を果たした」状態を作れる

ls8h.com から見ると、

  • 一次データの権利者に対しては「attribution を最終ユーザーまで運ぶ仕組みを用意した
  • 利用者に対しては「それを表示するために必要な情報は API で返している

この二段が成立する。これで、利用者がクレジット表示を怠った場合に責任の火の粉が再配信者の ls8h.com まで飛んでくる可能性を、設計上下げられる。

解決していないこと(フェアに)

attribution の同梱は、あくまでクレジット表示要件を構造的にクリアする仕組み。それだけでは片付かないものもある。

  • 商用利用禁止系のライセンス — attribution を出しても、用途自体が許されないケース
  • Share-Alike(継承)系 — 派生物にも同じライセンスを継承する義務がある
  • 改変明示義務 — 「データを改変した」事実を表示する条項があるフィードは別途対応が要る
  • レート制限・帯域コスト — これはライセンスとは別の運用論点

なので利用者側は、/api/v1/feeds で返ってきた attribution を UI に出すだけでなく、自分の用途が個別フィードのライセンスに照らして OK か はまた別途確認する必要がある。API 設計はそこまでは肩代わりしてくれない。

それでも、attribution の伝達経路を API の中に組み込んだことで、「個人プロジェクトでも公開しつづけられる」状態になっているのは確か。ライセンス遵守の作業を利用者と API 提供者で分担できる構造を持っていることが、認証不要で公開できている本当の理由だと思う。

それでも便利な理由

ここがいちばん面白いところだった。「オープンデータを集めて配ってるだけ」ならありがたみは薄い。実際は素の GTFS から経路検索までは距離がある。

レイヤー何が必要か
一次データ事業者ごとにバラバラの GTFS ZIP(数百本)
↓ 取り込み各カタログからの定期ダウンロード・差分管理
↓ 正規化駅・停留所を feedId:stopId で名前空間付きに統一、時刻はサービス日基準の秒数に統一
↓ 経路エンジン事業者またぎの乗り換えを成立させる接続関係の構築
Transit APIOpenAPI 1 本で叩ける、CORS 開放、認証なし

つまり、自分で同じことをやろうとすると、

  1. GTFS-Data.jpMobility Database から事業者単位の ZIP を全部ダウンロードしてきて
  2. パースして DB に入れて
  3. 駅同士の接続関係を構築して
  4. ダイクストラなり Raptor なりで経路探索を実装する

を全部やる必要がある。api.transit.ls8h.comこの一通りを済ませた状態を REST API で配ってくれている。価値は「データを持っていること」ではなく「集約・正規化・経路探索エンジン」のほうにある。

誰が出しているか

ls8h.com のトップに行くと、trkbt10 という個人開発者のポートフォリオサイトだった。連絡先は [email protected]。アイコンジェネレータや麻雀牌エンコードなど、複数の個人プロジェクトの一つとしてこの API が並んでいる。

OpenAPI には contactlicensetermsOfService も書かれていない。レート制限の明文記載もない。

なぜ出しているのか(仮説)

ここから先は本人に聞かないと分からないので推測。ただ、API の作りと個人ポートフォリオの雰囲気から、いくつか動機の組み合わせが透けて見える。

一番濃いのは 「自分が使いたいクライアントアプリの裏側として作った」 線。決め手は /api/v1/map/3d-scene と、/api/v1/guidance/planlive, tracking, strategy パラメータ。ナビゲーション UI を持つフロントエンドを作る前提でないと出てこない設計になっている。本人が自分のために建てた API を「ついでに認証なしで叩けるよ」状態にしてある、というのが一番自然な読み方。

もう一つ重なっているのが 技術ポートフォリオとしての側面。日本の経路検索 API は駅すぱあと・mixway・駅探など法人契約・有料の世界で、個人開発者が無料で同じことをやろうとすると、

  1. GTFS を全部集める
  2. 接続関係を構築する
  3. 経路探索エンジンを実装する

を全部一人でやらないといけない。これを完成させて動くものを公開しているのは、エンジニアとしての腕の証拠としてかなり強い。OpenAPI を整備して bunx openapi-typescript で typed client が生成できるところまで案内しているのも、他人に使ってほしい意思の表明として読める。完全に自分専用なら OpenAPI ドキュメントを綺麗に保つ必要はない。

メールアドレスが gmail.com で個人寄り、SLA も利用規約も書かれていないことから、商売にする気はなくて、無保証で公開しているだけというスタンスがそのまま出ている。

運用コストはどのくらいか

「個人で API を公開していると金がかかるんじゃないか」というのが次の疑問。実際に試算してみると、月数千円〜数万円のインフラ代で十分回せる規模に見える。

データ取り込み側は、GTFS フィードが数百本でも各数 MB〜数十 MB、全国分でも数十 GB レベル。R2 / S3 でストレージ代は月数百円。月次更新を cron で回すだけなので、パイプラインも VPS 1台で完結する。

経路検索エンジン側は、Raptor や CSA を使うと駅・時刻表をメモリに展開して爆速で引ける。全国 GTFS をオンメモリで持つと数 GB のメモリが要るので、メモリ多めの VPS(月 20〜40 くらい)が 1〜2 台あれば十分。レスポンスは JSON で 1リクエスト数 KB〜数十 KB、Cloudflare 経由なら egress も実質無料。

3D シーンは /api/v1/map/3d-scene の応答が タイル URL を返しているだけっぽいので、3D タイル実体は別 CDN にオフロードしていると思われる。本体の帯域は跳ねない設計。

唯一こわいのは「明示的なレート制限が書かれていない」点で、誰かが乱用したら CPU が刺さる。これはおそらく Cloudflare 側で粗いレート制限を入れて防いでいる、と読むのが自然。OpenAPI に書かれていないだけで、インフラ層で守っているのが実態に近いはず。

つまりこの API は、「個人が VPS 1〜2 台 + Cloudflare で完結させられる規模」。だから出せている。

オープンにする方が、個人にとっては実装が楽

もう一つ書いておきたいのが、**「オープンにする方がクローズドより実装が楽」**という、ちょっと直感に反する話。

クローズド(認証つき・課金あり)にすると、API そのもの以外に次のものが全部必要になる。

領域必要になるもの
認証ユーザー DB、サインアップ UI、メール認証、パスワードリセット、API キー発行・取り消し
課金Stripe 連携、サブスク管理、請求書発行、税務処理、未払い対応
法務利用規約、プライバシーポリシー、特定商取引法表記、GDPR/CCPA 対応
サポート「ログインできません」「請求が…」「API キーが漏れました」への対応窓口
監視ユーザー単位の使用量集計、不正検知、レート制限ロジック

これ全部、API コアの実装と同じかそれ以上の重さになる。しかも本業の経路検索の改善には1ミリも寄与しない。個人開発者からすると「作りたかったのは経路検索エンジンであって認証システムじゃない」というやつ。

オープンにすると、上の表ぜんぶ消える。代わりに必要になるのは「無保証で出してます」と書く一行と、コストを自腹で負担する覚悟だけ。

しかもこの API のケースでは、オープンの楽さを最大限享受できる条件が揃っている。

  1. 一次データがオープンデータ → 囲い込む法的・経済的理由がない
  2. 読み取り専用(GET のみ) → ユーザー単位の状態を持つ必要がない
  3. 個人プロジェクト → 収益化義務がない
  4. attribution 設計でライセンス義務を利用者に渡せる → 法務的なリスクも縮小

この 4 つが揃っているから「オープンが楽」が成立する。1 つでも欠けると(一次データが商用ライセンス、書き込み API がある、収益化が必要、など)話は変わってくる。

ただし代償もあって、

  • コストは全部本人負担
  • 利用者が増えても収入は増えない
  • 飽きたり生活が変わったりしたら止まる
  • 仕様変更・サービス終了の予告義務がない

つまり 「実装と運用は楽、ただし長期保証はない」 のがオープンの代償。業務サービスへの組み込みを軽率に勧められない理由はここにある。

触りかた

OpenAPI から TypeScript クライアントを生成するルートが用意されている。

bunx openapi-typescript https://api.transit.ls8h.com/api/openapi.json

座標から経路を引くだけなら、ブラウザのコンソールでこれが通る。

const r = await fetch(
  'https://api.transit.ls8h.com/api/v1/plan?from=geo:35.681,139.767&to=geo:35.658,139.701'
)
const json = await r.json()
console.table(json.itineraries)

使うときに気をつけること

最後にひとつだけ。これは個人プロジェクトであって、SLA も利用規約も明示されていない。手元のおもちゃに組み込む分にはとても便利だけれど、業務サービスに組み込むなら [email protected] にメールで運用方針を確認してからにしたほうが安全だと思う。仕様変更・サービス終了のリスクは利用側で負う前提で。

それと、レスポンスから取れる feed/operator ごとの attribution は、利用者の側で UI に出してあげるのが筋。API がわざわざその情報を返してくれているのは、利用者に渡してねという設計上のメッセージだと読める。


参考: