
## 無料公開部分
CityHeavenの予約APIを探している人向けに、実際の予約フローを分解してまとめた実測レポートです。
結論から言うと、CityHeavenのネット予約は「公開APIを1回叩けば予約できる」という構造ではありませんでした。
公開店舗ページから予約専用ドメインへ移り、カレンダー取得、空き枠選択、コース選択、ログイン導線、規約同意、連絡先入力、確認画面へ進む、複数段階のWebフローとして動いています。
このレポートを買う価値は、ここにあります。
- 自分でDevToolsを開いて何時間も追わなくても、予約導線の全体像を掴める
- 「単一APIではない」という前提を最初から理解できる
- 公開カレンダー側で読める情報と、サーバ側状態に近づく境界を分けて把握できる
- 実測ログ、redirect chain、endpoint分類、dry-runコード例をまとめて見られる
- 予約フロー調査や自動化検討で、どこが実用上の詰まりどころか分かる
以前、CityHeaven予約まわりの調査記録を低価格で公開したところ、約30件売れました。今回はその続編として、断片メモではなく「読めば予約フローの地図が手に入る」形に整理しています。
本編には、次の内容を入れています。
- 公開店舗ページから予約専用ドメインへ移る流れ
- カレンダー画面で取得できる空き枠情報
- 予約フローが単一APIではなく状態遷移で動く理由
- 未ログイン時にログイン導線へ戻される流れ
- 認証後に出てくる入力項目の種類
- 実測した所要時間
- method / path / status / redirect先 / note の実測ログ
- 公開カレンダーを読むdry-runコード例
- 確認画面手前で止まった理由
- 推測実装が失敗しやすいポイント
この無料部分では概要だけを書きます。具体的なendpoint表、実測ログ、コード例、redirect chainは有料本編に置いています。
## 有料本編
## まず結論
CityHeavenのネット予約は、次のような単純なものではありません。
```text
空き枠APIを叩く
-> コースIDを送る
-> 予約確定
```
実際には、公開ページ、予約専用ドメイン、カレンダーHTML、画面内JS変数、選択状態、コース選択、認証、連絡先入力、確認画面がつながったWebフローでした。
この構造を知らないまま調べると、次のような遠回りをします。
- カレンダーだけ見て「APIがありそう」と判断してしまう
- 空き枠選択と予約確定を同じものとして扱ってしまう
- コース選択後のredirectを見て、どこで認証が挟まるか分からなくなる
- 確認画面候補URLを直接開いて、有効期限切れで詰まる
- 実予約に近い操作と、公開情報の観測を混同する
このレポートは、その遠回りを避けるための地図です。
## 買うと分かること
### 1. 予約フローの全体像
実測で見えた流れは、次の通りです。
1. 公開店舗ページの予約入口を開く
2. ページ内の予約用iframeから予約専用ドメインへ移る
3. 予約対象の店舗・エリア・キャスト・条件が予約側へ引き継がれる
4. カレンダー画面で空き枠情報が取得される
5. 空き枠を選ぶと、予約確定前の選択状態がサーバ側セッションに保存される
6. コース選択画面に進む
7. 未ログイン状態ではCityHeaven側のログイン画面へ戻される
8. ログイン後は規約同意、連絡先入力へ進む
9. 今回の検証対象では、確認画面に入る前に店舗側設定エラーで停止した
10. 最終予約送信は実行していない
重要なのは、空き枠確認から予約確定までの間に、複数の画面遷移と状態保存が挟まっていることです。
「カレンダーを見る」「枠を選ぶ」「コースを選ぶ」「ログインする」「連絡先を入力する」は、技術的に同じ重さではありません。
### 2. 実測したステップと時間
未ログイン状態で、予約確定前までをHTTPベースで確認したときの実測値です。
```text
カレンダー取得 0.198秒
選択枠のセッション保存 0.154秒
コース選択画面の取得 0.170秒
コース選択後の未認証ログイン遷移 0.547秒
```
別条件での到達確認でも、予約確定前の主要ステップは概ね短時間で返ってきました。
```text
公開カレンダー取得 0.45秒
空き枠選択前後の状態確認 0.03秒
コース画面取得 0.26秒
未認証から認証導線への到達確認 0.40秒
```
この数字から分かるのは、「遅いから難しい」のではなく、「状態遷移が多いから雑に実装すると壊れる」ということです。
### 3. カレンダー画面で読める情報
カレンダー側では、日付・時間帯・空き状態に相当する情報が画面内データとして埋め込まれていました。
空き状態は、概ね次のように扱えます。
```text
○ / △ / ◎ 予約可能寄り
TEL 電話誘導
‐ / - 予約不可寄り
```
ここは実用上かなり重要です。
公開カレンダーを読むだけでも、「どの時間帯が候補になりそうか」は把握できます。一方で、枠を選択して次へ進むと、単なる閲覧ではなく、予約側セッションに状態を作る段階に入ります。
この違いを分けて理解できるだけでも、調査や設計の無駄がかなり減ります。
### 4. 観測したAPI形状
以下は公式公開APIではありません。公式Web予約画面が内部的に使っているHTTP通信を観測したものです。
#### 入口
店舗ページ側の予約入口は、店舗・エリア・キャストIDを含むページとして開かれます。
```text
GET https://www.cityheaven.net/{pref}/{area1}/{area2}/{shop}/A6ShopReservation/?girl_id={girl_id}
```
ページ内には予約用iframeがあり、そこから予約専用ドメインへ遷移します。
```text
GET https://www.cityheaven.net/{pref}/{area1}/{area2}/{shop}/S6ShareToReservationLogin/
?forward=F1
&girl_id={girl_id}
&pcmode=sp
```
この導線は、予約専用ドメイン側へ302します。
```text
Location: https://yoyaku.cityheaven.net/freservationresv/receive
?system_key=HEAVEN
&temporary_key=...
&user_id=...
&forward=F1
&prefectures={pref}
&area1={area1}
&area2={area2}
&directory_name={shop}
&girl_id={girl_id}
&user_kbn=FRN
```
`temporary_key` や `user_id` はログや販売資料には残すべきではありません。ここでは構造を理解するために、値を伏せて載せています。
#### カレンダー取得
女の子別カレンダーは次のような形で取得されます。
```text
GET https://yoyaku.cityheaven.net/calendar/{pref}/{area1}/{area2}/{shop}/1/{girl_id}?hv=n
```
HTML内には、カレンダー取得ベースURLや空き枠情報に相当するJS変数が埋め込まれていました。
```text
get_calendar_base_url = "/calendar/{pref}/{area1}/{area2}/{shop}"
get_result = "{...availability json...}"
```
`get_result` の中に、日付・時間帯・空きマークが含まれます。
このHTML内には、選択枠保存やコース選択導線に相当する値も含まれていました。ただし、そこから先はサーバ側状態に触れるため、実行可能なURLとpayloadの組み合わせとしては掲載しません。
#### 空き枠選択
カレンダー上の枠を選ぶと、選択枠がサーバ側セッションに保存されることを確認しました。
```text
POST 選択枠保存endpoint
Content-Type: application/x-www-form-urlencoded
```
これは予約確定ではありません。ただし、単なる閲覧でもありません。
この境界を理解しておくと、「どこまでなら公開情報の観測で、どこから状態変更に近いか」を整理できます。
#### コース選択
コース選択画面では、コースごとにPOST用フォームが並びます。
フォームには、コース識別子、コース名、所要時間、料金、無料予約可否、オプション関連の値などが含まれていました。
```text
POST コース選択endpoint
Content-Type: application/x-www-form-urlencoded
```
未ログイン状態でこのフォームを送ると、今回の実測では予約確定には進まず、ログイン導線へ戻されました。
```text
/select_option/...
-> /freservationresv/myhevenAuthc?...
-> https://www.cityheaven.net/{pref}/{area1}/{area2}/{shop}/S1ReserveLogin?pcmode=sp&forward=F3
```
このredirect chainを見ておくと、「どこでログインが挟まるのか」「未ログインでどこまで見られるのか」がかなり分かりやすくなります。
### 5. ログイン後に見えた項目
ログイン後は、規約同意を経て連絡先入力へ進みました。
この画面では、氏名、電話番号、メール、利用場所、連絡希望、自由入力欄、顧客属性に近い情報などが扱われます。
この段階から、単なるWebページ調査ではなく、個人情報や利用実態に近い情報が関わります。
本レポートでは、ログイン後フォームの具体フィールド名やPOST payloadは掲載しません。ただし、「どの段階から情報の性質が変わるか」は分かるように整理しています。
### 6. どこで止まったか
今回の認証後ドライランでは、ログイン、規約同意、連絡先入力までは到達しました。
ただし、連絡先入力後は確認画面に入る前に、店舗側設定変更を示すエラーで停止しました。
```text
EFRESV020809
申し訳ございません。お店の設定が変更されました。
お手数ですが、TOPページから再度予約の手続きを行ってください。
```
ここで分かったのは、確認画面や最終送信をURLだけ見て推測実装しても、うまくいかない可能性が高いということです。
画面上は確認画面候補や重複確認候補らしき導線が見えても、正規のセッション遷移を通らずに直接開くと有効期限切れになります。
この「最後のほうが単純ではない」という実測結果は、調査価値が高い部分です。
### 7. dry-run用コード例
本編には、公開カレンダーを読み、候補枠を出力するdry-runコードの核も載せています。
目的は「予約する」ことではなく、「公開カレンダーから読み取れる情報を機械的に整理する」ことです。
```python
#!/usr/bin/env python3
from __future__ import annotations
import json
import re
import sys
import time
import urllib.parse
import urllib.request
OPEN_MARKS = {"○", "△", "◎"}
DANGEROUS_PATH_PARTS = (
"/confirm",
"/complete",
"/reservation_confirm",
"/reservationrequest",
"/register",
)
def ensure_not_dangerous(url: str) -> None:
path = urllib.parse.urlparse(url).path.lower()
if any(marker in path for marker in DANGEROUS_PATH_PARTS):
raise RuntimeError(f"blocked dangerous endpoint: {url}")
def js_var(html: str, name: str) -> str:
match = re.search(
r"var\\s+" + re.escape(name) + r"\\s*=\\s*(['\\\"])(.*?)\\1\\s*;",
html,
re.S,
)
if not match:
raise RuntimeError(f"JS variable not found: {name}")
return match.group(2)
def get(url: str) -> tuple[str, float]:
ensure_not_dangerous(url)
started = time.perf_counter()
req = urllib.request.Request(
url,
headers={
"User-Agent": "Mozilla/5.0",
"Accept-Language": "ja,en-US;q=0.7,en;q=0.3",
},
method="GET",
)
with urllib.request.urlopen(req, timeout=10) as res:
body = res.read().decode("utf-8", "replace")
return body, round(time.perf_counter() - started, 3)
def first_open_slot(get_result: dict) -> dict:
for day_group in get_result.get("commu_acp_status", []):
for day, slots in day_group.items():
for slot in slots:
if slot.get("acp_status_mark") not in OPEN_MARKS:
continue
raw_time = str(slot.get("have_start") or slot.get("time") or "").zfill(4)
return {
"day": day,
"day_time": f"{raw_time[:2]}:{raw_time[2:]}-",
"mark": slot.get("acp_status_mark"),
}
raise RuntimeError("open slot not found")
def summarize_calendar(calendar_url: str) -> dict:
html, elapsed = get(calendar_url)
get_result = json.loads(js_var(html, "get_result"))
slot = first_open_slot(get_result)
return {
"calendar_get_elapsed_sec": elapsed,
"first_open_slot_candidate": {
"day": slot["day"],
"time": slot["day_time"],
"mark": slot["mark"],
},
"boundary": "calendar_read_only",
"redacted_next_phase": "slot selection creates server-side session state",
"safety": {
"builds_post_payload": False,
"submits_selected_list": False,
"submits_contact_info": False,
"submits_final_reservation": False,
},
}
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: python safe_probe.py CALENDAR_URL", file=sys.stderr)
raise SystemExit(2)
print(json.dumps(summarize_calendar(sys.argv[1]), ensure_ascii=False, indent=2))
```
実行結果のイメージは次のようになります。
```json
{
"calendar_get_elapsed_sec": 0.198,
"first_open_slot_candidate": {
"day": "2026-05-31",
"time": "16:00-",
"mark": "○"
},
"boundary": "calendar_read_only",
"redacted_next_phase": "slot selection creates server-side session state",
"safety": {
"builds_post_payload": false,
"submits_selected_list": false,
"submits_contact_info": false,
"submits_final_reservation": false
}
}
```
## このレポートの使いどころ
この資料は、次のような人に向いています。
- CityHeavenの予約フローが実際にどう動くか知りたい
- 予約APIらしきものを探したが、画面遷移が多くて整理できなかった
- 公開カレンダーから読める情報を把握したい
- 自分でDevToolsを追う前に、全体像を短時間で掴みたい
- 予約系Webフロー調査の実例を見たい
- API調査レポートを商品化する際の粒度感も知りたい
反対に、次の目的には向きません。
- 予約を自動で確定させるコードが欲しい
- ログイン情報やCookieを使った予約代行をしたい
- CAPTCHA/SMS回避を探している
- 複数予約、重複予約、キャンセル自動化をしたい
- 店舗の予約枠を占有する方法を知りたい
## なぜ買う価値があるか
このレポートの価値は、「自分で試せば分かるかもしれないこと」を、実測済みの地図として読めることです。
自分で追う場合、公開店舗ページ、iframe、予約専用ドメイン、JS変数、redirect、セッション、ログイン導線を順番に見ていく必要があります。しかも、どこから先が状態変更に近いかを判断しながら進める必要があります。
本レポートでは、その調査結果を次の形に整理しています。
- 予約フローの全体図
- 観測できたendpoint分類
- 画面内データの読み取り方
- 実測時間
- redirect chain
- dry-runコード
- 詰まった箇所と理由
- 最終送信を推測しないほうがよい理由
調査時間を短縮したい人、予約フローの構造を先に理解したい人、API調査コンテンツの作り方を知りたい人には、1,980円以上の価値がある内容にしています。
## 価格
初版価格は1,980円です。
今後、追記して内容を増やす場合は、標準価格を2,980円に上げる予定です。初版のうちに買った人は、その時点の内容を前提に読めます。
## 注意
本レポートは、公開ページおよび自分の検証環境で観測した範囲を整理したものです。CityHeaven運営・店舗・関連サービスから許可を受けた公式資料ではありません。
実予約の最終送信、キャンセル操作、ログイン情報やCookieの共有、CAPTCHA/SMS回避、店舗業務に影響する操作は扱いません。
デジタル商品の性質上、購入後の返金は原則として行いません。ただし、ファイル破損や重複決済など、販売者側で対応すべき問題がある場合は個別に対応します。
## まとめ
CityHeavenのネット予約は、単一APIではなく、公開ページ、予約専用ドメイン、カレンダー、選択状態、コース選択、ログイン、連絡先入力、確認画面がつながったWebフローです。
このレポートでは、その構造を実測ログとコード例つきで整理しました。
自分で危ないところまで試す前に、まず全体像と詰まりどころを把握したい人向けの調査レポートです。
この記事を書いたライター
しなやかなレッサーパンダ115