大阪万博 API 解説
大阪万博が昨日閉幕したので、大阪万博の予約サイトの API について自分が調べたことを公開しようと思います。
数日前にタイムスリップした気持ちでお読みください。
また、脆弱だと思ったポイントもあるので、閉幕したのでそれも解説しちゃいます。
想定読者
- 万博のアプリの裏側をちょっと覗いてみたい人
- 大阪万博の脆弱っぽい部分をちょっと知りたい人
- 閉幕してしまったけれど自分がどこに行ったかを思い出したい人
- 次日本で万博が開催されたときにアプリを開発する人、および発注する人
tl;dr
成果物: https://github.com/pnsk-lab/myakumyakujs
認証の仕様
二段階認証
大阪万博では、二段階認証が有効化されています。しかし、16 歳未満のアカウントは二段階認証を有効化する必要がないようで、そのおかげで楽に API をたたくことができました。
認証方法
認証に必要なページは待合室で保護されているので、その URL を得るのに待合室を通過する必要があります。
まず、ログインページの URL は動的に変化するので、その URL を取得してあげる必要があります。
https://ticket.expo2025.or.jp/api/d/expo_login
というエンドポイントにアクセスすると、ログインページの URL にリダイレクトされます。
const redirectPage = await fetch('https://ticket.expo2025.or.jp/api/d/expo_login', {
redirect: 'manual'
})
const loginPageUrl = redirectPage.headers.get('Location')
次に、ログインページの HTML を取得して、そこから <form>
タグの action
属性を取得します。
const loginPage = await fetch(loginPageUrl, {
redirect: 'manual'
})
const targetUrl = (await loginPage.text()).match(/(?<=action=")[^"]+(?=")/)?.[0].replaceAll('amp;', '')
最後に、targetUrl
にメールアドレスとパスワードを POST します。すればいいのですが、ここで気を付けなければならないポイントがあります。それは Cookie です。先ほどのログインページとリダイレクトページの Cookie を引き継いであげる必要があります。
また、この画面では待合室が有効化されているので、リダイレクトを手動で処理してあげる必要があります。
こんな感じで while ループを回して、リダイレクトが終了するまで待ちます。リクエストは application/x-www-form-urlencoded
で送信します。username, password のほかに credentialId という謎のパラメーターがありますが、それは空文字列でいけました。
ここで取得しておくべき Cookie は、session_id
のみです。なぞに AUTH_SESSION_ID みたいな Cookie や、 SESSION みたいな Cookie も発行されますが、これらは無視して大丈夫だった記憶です。
チケットの仕様
取得方法
万博のチケットの仕様を解説します。
https://ticket.expo2025.or.jp/api/d/my/tickets/?count=1
に GET を Cookie つきで送るだけです。簡単ですね。
返ってくるのは JSON で、チケットの配列です。
チケットの例
{
"id": 12345678, // チケットの数値 ID
"ticket_id": "AAAAAAAAAAA", // チケット ID
"agent_code": "0010", // 謎
"item_group_code": "0012", // 謎
"item_code": "00452", // 謎
"item_group_order": 19, // 謎
"item_order": 2, // 謎
"simple_ticket_id": "AAAAAAAAAAA", // 何が違うん
"disp_status": 3, // 謎
"item_group_name": "Summer Pass", // 夏パスだったのでこれ
"item_name": "Summer Pass", // 同上
"item_abb_name": "12-17", // 12-17 歳用のチケットだったのでこれ
"item_summary": "Youth (aged from 12 to 17)", // 同上
"image_large_path": "/tickethub_file/images/0433/00433/0010/0/item_large_image/9b3cc94808d9c04aee090190ab0123c398b8.gif", // 動く夏パスの画像
"order_number": null, // 謎
"receive_type": 2, // 謎
"received_at": "2025-07-15 16:59:16 +0900", // たぶんチケットを受け取った日時
"schedules": [
{
"user_visiting_reservation_id": 1234567, // 入場予約 ID じゃない?
"use_state": 1, // 謎
"entrance_date": "20250812", // 入場日
"gate_type": 2, // たぶん入場ゲートの種類。東か西か
"schedule_name": "10:00-", // 何時から入場できるか
"start_time": "0900", // 上と何が違うんだ
"proxy_reserve": true, // 代理予約かどうか?
// 以下は不明
"group_ticket_qr_divi": 0,
"admission_time": "104227",
"admission_buf_during": false,
"empty_frame": false,
"on_the_day": true,
"month_lottery": false,
"day_lottery": false,
"lotteries": { "month": [], "day": [] }
},
// ... 他の日の入場予約も同様の形式で入る
],
"event_schedules": [
{
"id": 1234567, // 予約 ID かも
"schedule_code": "2025081706HOH0",
"schedule_no": "06",
"schedule_name": "11:40-12:00",
"program_code": "HOH0",
"entrance_date": "20250817",
"start_time": "1140",
"end_time": "1200",
"suspend_divi": 0,
"updated_at": "2025-10-14 02:10:56 +0900",
"created_at": "2024-09-19 21:15:51 +0900",
"event_code": "HOH0",
"event_name": "BLUE OCEAN DOME",
"event_summary": "The theme of Blue Ocean Dome is “Reviving the Ocean”.With the “consciousness transforming devices” that will cause an attitude change toward the earth and oceans,The Blue Ocean Dome will offer an unprecedented experience to visitors through various events related to the sustainable use of the oceans in the exhibition space .",
"virtual_url": "https://contents.ssv.virtualexpo.expo2025.or.jp/deeplink/cushion_page.html?SpaceId=SS-108932",
"virtual_url_desc": "Click here for the Virtual Expo",
"portal_url": "https://www.zeri.jp/expo2025/",
"portal_url_desc": "Click here for details",
"ticket_id": "ADFGHJJKL",
"registered_channel": 5,
"proxy_reserve": false,
"use_state": 1,
"group_ticket_qr_divi": 0,
"program_ticket_state": 1,
"admission_time": "114452",
"admission_buf_during": false
}
// ... 他のイベントも同様の形式で入る
],
"ticket_type_id": "22",
"change_reservation": 0,
"fast_lottery": false,
"lotteries": { "fast": [] },
"adult_type": true,
"disp_bid_status": 2
}
ちなみにこの image_large_path
はこれです、かわいいですね。
あと、この API は閉幕後の今でも動いています。止まる前に思い出を取っておきたい人は今のうちに JSON を保存しておきましょう。
イベント情報の仕様
認証方法なんてどうでもよくて、本題はここからです。
まず、"イベント" についてです。この "イベント" というのは、各パビリオンで行われる入場方法を意味します。
例えば、
電力館
|-- 通常入場
|-- 車いす入場
null²
|-- インスタレーションモード
|-- ダイアログモード
|-- ウォークスルーモード
のように各パビリオンは異なる形式を提供しますが、それぞれ別の "イベント" として扱われます。
イベントの取得 API
イベントの取得 API は https://ticket.expo2025.or.jp/api/d/events
です。これに認証済みの Cookie をつけて GET します。
URL パラメーターは以下の通りです。
-
ticket_ids[]
: カンマ区切りでチケット ID を指定します。- 例:
T1CKET,T2CKET
- このパラメーターは個人的にうわってなったランキング上位です。
- 例:
-
entrance_date
: 入場日を指定します。YYYYMMDD
形式の文字列です。- 例:
20250413
- 例:
-
count
: これはなにか忘れました。1
を指定しておけばよいと思います。 -
limit
: 取得する最大件数だったと思います。例:20
-
event_type
: これはわからなかったか忘れました。0
を指定しておけばよいと思います。 -
channel
: チャンネル。後で解説します。 -
event_name
: これは必須ではありません。これ event_name っていう名前しておきながら検索クエリなんですよ? query とかにすべきだったと思います。
例えばすべてのイベントを ReadableStream
で返す関数は以下のようにできます:
channel
について
channel は予約の方法を表します。以下の enum を見てもらえばいいと思います
- 0: 来場日時予約(指定した日時の予約)
- 1: 超早割特別抽選
- 2: 2か月前抽選
- 3: 7日前抽選
- 4: 空き枠先着予約 (いわゆる 3日前予約)
- 5: 当日登録
それぞれの値は予約方法の違いを表しています。
また、後ほど書くのですが、なんと 3 日前予約の情報を 3 日前以前から取得できてしまうのですw
イベントが持つ状態
チャンネルについて説明します。
ここでは null² のナオライモードのイベントを例にします。
{
"id": 49420,
"event_code": "IC0C",
"event_name": "Signature Pavilion OCHIAI Yoichi 'null²' Naorai Mode *10 min before the reservation start time",
"program_code": "IC0C",
"event_summary": "[AverageDuration:10min][10 min before the reservation start time]Naorai Mode – Farewell to the Forest of NullThis 10-minute ceremony is the final Naorai shared by the pavilion and its visitors. Soon, null² will return to Nul, and we celebrate its departure through a feast. In a space that shifts from the solemn resonance of a Buddhist indō (ritual of farewell) to the lively rhythms of festival applause and cheers, all participants become one community, sharing “goodbye” and “thank you.” It is not an end, but a prayer and a hospitable rite for a new beginning.",
"virtual_url": "https://contents.ssv.virtualexpo.expo2025.or.jp/deeplink/cushion_page.html?SpaceId=SS-664929",
"virtual_url_desc": "Click here for Virtual Expo ",
"portal_url": "https://expo2025.digitalnatureandarts.or.jp/specialsite.html",
"portal_url_desc": "Click here for details",
"date_status": 2
}
それぞれ解説していきますね。
-
id
: イベントの数値 ID です。使われているところを見たことがないです。 -
event_code
: イベントコードです。API でイベントを指定するときに使います。 -
event_name
: イベントのタイトルです。 -
program_code
: これは謎です。イベントコードと異なる値が入っていることを見たことがないです。 -
event_summary
: イベントの説明です。 -
virtual_url
: VR の大阪万博の URL です。 -
virtual_url_desc
: VR の大阪万博のリンクテキストです。これわざわざ API で返す必要ある? -
portal_url
: 大阪万博のポータルサイトの URL です。 -
portal_url_desc
: ポータルサイトのリンクテキストです。これもわざわざ API で返す必要ある? -
date_status
: これはイベントの空き状態です。-
0
: すいてる -
1
: もうすぐ埋まる -
2
: 予約不可
-
イベントの詳細な空き情報
以上の例では一日の中で空きがあるかどうかしかわかりませんが、実際には時間帯ごとの空き情報も取得できます。
エンドポイントは https://ticket.expo2025.or.jp/api/d/events/:event_code
です。:event_code
の部分にはイベントコードを入れます。
また、パラメータは以下の通りです。
-
ticket_ids[]
: カンマ区切りでチケット ID を指定 -
entrance_date
: 入場日をYYYYMMDD
形式で指定 -
channel
: 予約方法を指定
例えば、閉幕日である 10/13 の null² のナオライモードの空き情報の結果は以下のような JSON です:
{
"event_code": "IC0C",
"event_name": "Signature Pavilion OCHIAI Yoichi 'null²' Naorai Mode *10 min before the reservation start time",
"event_summary": "[AverageDuration:10min][10 min before the reservation start time]Naorai Mode – Farewell to the Forest of NullThis 10-minute ceremony is the final Naorai shared by the pavilion and its visitors. Soon, null² will return to Nul, and we celebrate its departure through a feast. In a space that shifts from the solemn resonance of a Buddhist indō (ritual of farewell) to the lively rhythms of festival applause and cheers, all participants become one community, sharing “goodbye” and “thank you.” It is not an end, but a prayer and a hospitable rite for a new beginning.",
"image_large_path": null, // 謎
"image_small_path": null,
"thumbnail_image_path": "/tickethub_file/images/0433/00433/0010/0/item_thumbnail_image/5507accb056d6042cb08f7204976029dd089.png",
"virtual_url": "https://contents.ssv.virtualexpo.expo2025.or.jp/deeplink/cushion_page.html?SpaceId=SS-664929",
"virtual_url_desc": "Click here for Virtual Expo ",
"portal_url": "https://expo2025.digitalnatureandarts.or.jp/specialsite.html",
"portal_url_desc": "Click here for details",
"event_schedules": {
// ...
"1820": {
"schedule_code": "2025101301IC0C",
"schedule_no": "01", // 謎
"schedule_name": "18:20-18:30", // 予約時間帯
"program_code": "IC0C", // コード、ここに入れる必要ある?
"entrance_date": "20251013", // これも必要ある?
"start_time": "1820", // 予約時間帯の開始時間
"end_time": "1830", // 予約時間帯の終了時間
"time_status": 2, // さっきと同じ。0: すいてる, 1: もうすぐ埋まる, 2: 予約不可
"unavailable_reason": 1 // 解析してない
},
"1830": {
"schedule_code": "2025101302IC0C",
"schedule_no": "02",
"schedule_name": "18:30-18:40",
"program_code": "IC0C",
"entrance_date": "20251013",
"start_time": "1830",
"end_time": "1840",
"time_status": 2,
"unavailable_reason": 1
}
},
"reserve_now": 1 // 謎
}
イベントの予約 API
さて、もしみなさんが万博開催中にタイムスリップしたとして一番欲しいのは予約 API ですよね (意味深)
エンドポイントは、https://ticket.expo2025.or.jp/api/d/user_event_reservations
です。
POST で application/json
を送信します。例は以下です:
{
"ticket_ids": ["T1CKET", "T2CKET"], // チケット ID の配列。これがカンマ区切りかどうか夜悩んだのはいい思い出
"entrance_date": "20251013", // 入場日
"start_time": "1820", // 予約したい時間帯の開始時間
"event_code": "IC0C", // 予約したいイベントコード
"registered_channel": "4" // これは channel と同じ意味
}
このレスポンスはどこにもメモしていないのでもうわかりませんが、成功すると 2xx 系が返ってきた記憶はあります。
脆弱だなーリスト
私が大阪にいるとき脆弱だなーってなりました
-
3 日前予約の情報を 3 日前以前から取得できる
- UI 上だと 3 日前の 0 時から予約が解放されてどのくらいとれるか見られるけど、API ではそれ以前から見られる
- これだめじゃね????
- でも狙いを定めることができたので個人的にラッキー
-
二段階認証が 16 歳未満のアカウントで無効化できる
-
待合室が API 経由でバイパスされる
- UI にしか待合室が適用されないので、API だと待たずに操作し放題
-
こういうのもあるらしい: https://x.com/j416dy/status/1977690789863157811
応用例
TypeScript クライアントライブラリ
私はこの API を使って、TypeScript クライアントライブラリをつくりました (publish してないです):
こんな感じで使えます
import { fetchEvents, login, ReservationChannel } from 'jsr:@pnsk-lab/myakumyakujs'
const sess = await login(userId, password)
const events = await Array.fromAsync(fetchEvents(sess, {
ticket: tickets[0],
channel: ReservationChannel.SAME_DAY_REGISTRATION,
entranceDate: '20250812',
}))
console.log(events)
MCP
GitHub Copilot に万博の空きを聞くことができます。
cli
CLI を作成しました。
もちろん未来の空き情報を見れるので、大活躍です。
感想
早めに万博行けばよかった。来年ないの寂しい
Discussion