Appearance
API-009 おすすめ台 REST
概要
おすすめ台担当 bot が、ホールの台データを読んでおすすめ台の案を作り、下書きとして保存するための REST。おすすめ台担当ロール(recommend_picker、Capability read_recommend_data / write_recommend_draft)のユーザーがアプリケーションパスワードで呼ぶ。
- 対象ホールは
HallEnum::get_ana_slo_halls()(island/espasu/bigapple)。どのホールも案は最大 20 台 - bot が書き込めるのは案の下書き(
db_recommendation_draft)だけ。記事への反映・公開はしない - 案の承認・却下は管理画面(ADM-037、
manage_options)だけで行う。管理画面は開いたときのrevisionと一致するdraftの行だけを書き換えるため、その間に bot が上書きした案は承認されない。承認・却下済みの案は bot から上書きできない - BB / RB は返さない。台データは差枚(
samai)と G 数(g)だけを使う - 運用確認・日別記事編集・X告知用の REST とは namespace を分ける。アカウントとアプリケーションパスワードも分ける
案の項目(draft)
| field | 型 | 説明 |
|---|---|---|
id | int | 案の ID |
hall | string | ホール(HallEnum::value) |
target_date | string | おすすめ対象日(Y-m-d) |
method_version | string | 選定ロジックの版 |
items | array | おすすめ台(下記)。rank の昇順 |
note | string | bot のメモ |
status | string | draft(下書き)/ approved(承認済み)/ rejected(却下) |
revision | int | 版。作成時は 1 で、上書きするたびに 1 増える |
created_by | int | 最後に保存したユーザー ID |
approved_by | int|null | 承認・却下したユーザー ID。未判断は null |
created_at | string | 作成日時(DB の created_at) |
updated_at | string | 更新日時(DB の updated_at) |
results_available | bool | 対象日の台データがあるか。API-009-1 だけが付ける |
おすすめ台の項目(items[])
| field | 型 | 説明 |
|---|---|---|
rank | int | 順位(1〜20) |
dainum | string | 台番号(数字 1〜6 桁) |
kishu_id | int | 機種 ID(db_kishu_master.id) |
score | float | bot が付けた点数(小数 4 桁に丸める) |
reason_code | string | 選んだ理由の種類(英小文字・数字・_、32 文字以内。値の一覧は bot 側で決める) |
reason_text | string | 選んだ理由(200 文字以内) |
result | object|null | 対象日の結果 {kishu_id, samai, g}。API-009-1 だけが付ける。台データが無い・台が無いときは null |
API-009-1 GET /drafts
保存済みの案と、対象日の台データがあればその結果を返す。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | はい | island / espasu / bigapple | ホール |
target_date | はい | Y-m-d | おすすめ対象日 |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
hall | string | ホール |
target_date | string | 対象日 |
draft | object|null | 案の項目。保存されていなければ null |
HTTP ステータス: 200。items[].result は db2023 の対象日・ホールの行を台番号で引く。台番号が同じでも機種が入れ替わっている場合があるため、result.kishu_id と案の kishu_id を比べて判断すること。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
hall が無い・対象ホール以外 | 400 | RecommendRestCopy::HALL_INVALID |
target_date が無い・Y-m-d でない・存在しない日付 | 400 | RecommendRestCopy::TARGET_DATE_INVALID |
| DB の読み込みで例外が起きた | 503 | RecommendRestCopy::READ_FAILED |
API-009-2 POST /drafts
案を保存する。ホール×対象日ごとに 1 件で、draft の間は何度でも上書きできる。
入力(リクエスト)
JSON ボディ。
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | はい | island / espasu / bigapple | ホール |
target_date | はい | Y-m-d。サイトのタイムゾーンで今日の 31 日前から 31 日後まで | おすすめ対象日 |
method_version | はい | 英数字と . _ -、1〜32 文字 | 選定ロジックの版 |
items | はい | 1〜20 件の配列(下記) | おすすめ台 |
note | いいえ | 文字列、200 文字以内 | bot のメモ。省略時は空文字 |
items[]:
| param | 必須 | 型・制約 |
|---|---|---|
rank | はい | 1〜20 の整数 |
dainum | はい | 数字 1〜6 桁の文字列(正の整数も受け付けて文字列にする) |
kishu_id | はい | 正の整数 |
score | はい | 数値(JSON の number。絶対値 1,000,000 以下) |
reason_code | はい | 英小文字・数字・_、1〜32 文字 |
reason_text | いいえ | 文字列、200 文字以内 |
rank と dainum はそれぞれ重複できない。保存時は rank の昇順に並べ替える。
reason_text と note は受け取った文字列をそのまま保存する(HTML の除去はしない)。管理画面(ADM-037)など表示する側で必ずエスケープすること。
台番号と機種は、ホールの db2023 最新取込日のデータと照合する。最新取込日にその台番号が無い、または機種 ID が違う台が 1 件でもあれば保存しない(入れ替え前の台番号で案を作るのを防ぐため)。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
result | string | created(新規保存)/ updated(下書きを上書き) |
draft | object | 保存後の案の項目(results_available と result は付けない) |
HTTP ステータス: created は 201、updated は 200。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
hall が無い・対象ホール以外 | 400 | RecommendRestCopy::HALL_INVALID |
target_date が無い・Y-m-d でない・存在しない日付 | 400 | RecommendRestCopy::TARGET_DATE_INVALID |
target_date が今日の 31 日前より前・31 日後より後 | 400 | RecommendRestCopy::TARGET_DATE_OUT_OF_RANGE |
method_version が上記の形式でない | 400 | RecommendRestCopy::METHOD_VERSION_INVALID |
note が文字列でない・200 文字を超える | 400 | RecommendRestCopy::NOTE_INVALID |
items が無い・配列でない・空・21 件以上・キー付きの連想配列 | 400 | RecommendRestCopy::ITEMS_INVALID |
items[] のどれかが上記の形式でない(メッセージに添字が入る) | 400 | RecommendRestCopy::ITEM_INVALID |
rank または dainum が重複 | 400 | RecommendRestCopy::ITEM_DUPLICATE |
| 最新取込日に無い台番号・機種 ID が違う台がある(メッセージに順位が入る) | 400 | RecommendRestCopy::UNITS_INVALID |
| ホールの台データが 1 日分も無い | 409 | RecommendRestCopy::NO_UNIT_DATA |
| 案が承認済み・却下済み | 409 | RecommendRestCopy::DRAFT_LOCKED |
| DB に保存できなかった・DB の読み書きで例外が起きた | 503 | RecommendRestCopy::SAVE_FAILED |
上書きは status = 'draft' の行だけを対象にし、上書きするたびに revision を 1 増やす。保存の直前に管理画面で承認・却下された場合も 409 になる。同じホール・対象日を同時に新規保存して INSERT が重複キーで失敗したときは、読み直して draft なら上書きする(結果は updated)。例外は error_log に 1 行(RecommendRestCopy::LOG_ERROR_FORMAT)残し、監査ログの outcome は failed になる。
監査ログ
BotRestAuditLogger で error_log に 1 行 JSON(接頭辞 [bot-rest-audit])を書く。記録するのはルート・ユーザー ID・hall・target_date・件数(item_count)・method_version・結果(outcome: created / updated / locked / invalid_units / no_unit_data / failed)だけ。reason_text と note は記録しない。入力検証で 400 になったリクエスト(台番号の照合で 400 になったものを除く)は記録しない。
台データの項目(items[])
API-009-4・5・6 で返す db2023 の 1 行。BB / RB は返さない。
| field | 型 | 説明 |
|---|---|---|
date | string | 日付(Y-m-d) |
dainum | string | 台番号 |
kishu_id | int | 機種 ID(db_kishu_master.id) |
kishu | string | 機種名(マスタの正規名称。マスタに無いときは 未紐づけ) |
samai | int | 差枚 |
g | int | G 数(db2023 の kaiten) |
samai_step | int | その日・ホールの差枚の刻み(150 / 50 / 1。下記) |
samai_step は、その日・ホールの台データのうち差枚が 0 でない行がすべて 150 の倍数なら 150、50 の倍数なら 50、それ以外は 1(判定に使える行が無い日も 1)。差枚が -4900 以下の行は下限への張り付き(アイランドの -5000 / -4988 / -4954 / -4919 など。丸めの刻みに揃わない)とみなし、判定に使わない。差枚が丸められて取り込まれている日があり、刻みは 1 台の行からは決まらないため、ホール全体の行から求める(API-009-5 の 1 台分の履歴でも同じ値)。刻みが 50 や 150 の日の差枚は丸めた値なので、samai が 0 の行も「0 前後」の意味になる。取り込み途中の日は、残りの行が入ると値が変わることがある。値はクエリのたびに DAY の範囲とホールで集計する(RecommendUnitDataRepositoryInterface::list_samai_steps)。
API-009-3 GET /coverage
ホールごとに、台データがある最初の日・最新日と、最新日までの直近 days 日で台データが無い日を返す。bot はデータが揃っているかをここで確かめてから案を作る。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | いいえ | island / espasu / bigapple | 省略時は 3 ホールすべて |
days | いいえ | 1〜92 の整数 | 欠けている日を探す日数(最新日を含む)。既定は 31 |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
days | int | 受け付けた days |
items[].hall | string | ホール |
items[].first_day | string|null | 台データがある最初の日。データが無いホールは null |
items[].latest_day | string|null | 台データがある最新日 |
items[].checked_from | string|null | 欠けている日を探した最初の日(最初の日より前にはさかのぼらない) |
items[].missing_dates | array | checked_from から latest_day までで台データが無い日(Y-m-d、昇順) |
HTTP ステータス: 200。休業日も missing_dates に入る(休業日かどうかは判定しない)。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
hall が対象ホール以外 | 400 | RecommendRestCopy::HALL_INVALID |
days が 1〜92 の整数でない | 400 | RecommendRestCopy::DAYS_INVALID |
| DB の読み込みに失敗した | 503 | RecommendRestCopy::READ_FAILED |
API-009-4 GET /units
期間内の台データをページ単位で返す。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | はい | island / espasu / bigapple | ホール |
date_from | はい | Y-m-d | 開始日(含む) |
date_to | はい | Y-m-d | 終了日(含む) |
page | いいえ | 正の整数 | 既定は 1 |
per_page | いいえ | 1〜2000 の整数 | 既定は 500 |
期間は両端を含めて最大 31 日(RecommendUnitsRestController::UNITS_MAX_RANGE_DAYS)。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
hall | string | ホール |
date_from | string | 開始日 |
date_to | string | 終了日 |
page | int | ページ |
per_page | int | 1 ページの件数 |
total | int | 期間内の全件数 |
total_pages | int | 全ページ数 |
items | array | 台データの項目。日付・台番号(数値順)の昇順 |
HTTP ステータス: 200。total_pages を超えたページは items が空になる。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
hall が無い・対象ホール以外 | 400 | RecommendRestCopy::HALL_INVALID |
日付が無い・Y-m-d でない・存在しない日付・date_from が後 | 400 | RecommendRestCopy::DATE_RANGE_INVALID |
| 期間が 31 日を超える | 400 | RecommendRestCopy::DATE_RANGE_TOO_LONG |
page が正の整数でない・per_page が 1〜2000 の整数でない | 400 | RecommendRestCopy::PAGE_INVALID |
| DB の読み込みに失敗した | 503 | RecommendRestCopy::READ_FAILED |
ページは OFFSET で切るため、取り込み中の日を含む期間をページ送りすると行がずれることがある。取り込みが終わった日(/coverage の latest_day まで)を指定する。
API-009-5 GET /units/{dainum}/history
1 台の直近 days 日(ホールの最新日まで)の台データを返す。台番号が同じでも途中で機種が入れ替わることがあるため、前の行から機種 ID が変わった行に kishu_changed: true を付ける。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
dainum | はい | 数字 1〜6 桁(パス) | 台番号 |
hall | はい | island / espasu / bigapple | ホール |
days | いいえ | 1〜180 の整数 | 最新日を含む日数。既定は 60 |
台番号はパスの値だけを使う(クエリ・ボディの dainum は無視する)。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
hall | string | ホール |
dainum | string | 台番号 |
date_from | string|null | 開始日。ホールに台データが無いときは null |
date_to | string|null | ホールの最新日。ホールに台データが無いときは null |
count | int | items の件数 |
kishu_changes | int | kishu_changed が true の行の数 |
items | array | 台データの項目に kishu_changed(bool)を足したもの。日付の昇順 |
HTTP ステータス: 200。台番号が無い・期間内にデータが無いときも 200 で items が空。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
dainum が数字 1〜6 桁でない | 400 | RecommendRestCopy::DAINUM_INVALID |
hall が無い・対象ホール以外 | 400 | RecommendRestCopy::HALL_INVALID |
days が 1〜180 の整数でない | 400 | RecommendRestCopy::DAYS_INVALID |
| DB の読み込みに失敗した | 503 | RecommendRestCopy::READ_FAILED |
API-009-6 GET /export
1 年分の台データを 1 か月ずつ NDJSON(1 行 1 JSON)で返す。bot が手元で分析するための一括取得で、他の GET より厳しいレート制限をかける。毎日の取り直しは since でその日以降だけを取る(Issue #3918)。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | はい | island / espasu / bigapple | ホール |
year | はい | 2020〜今年の整数 | 年 |
cursor | いいえ | YYYY-MM(year と同じ年) | 取得する月。省略時は 1 月(since があれば since の月)。前回の next_cursor をそのまま渡す。since があるときは since の月以降 |
since | いいえ | Y-m-d(year と同じ年、今日まで) | この日以降の行だけを返す。since の月より後の月(cursor)では月全体を返す |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
hall | string | ホール |
year | int | 年 |
month | string | 返した月(YYYY-MM) |
since | string|null | 受け付けた since(Y-m-d)。指定なしは null |
format | string | ndjson |
count | int | ndjson の行数 |
ndjson | string | 台データの項目を 1 行ずつ JSON にして改行でつないだ文字列(末尾も改行)。0 件は空文字 |
next_cursor | string|null | 次の月。12 月、または今年の今月を返したときは null |
HTTP ステータス: 200。今月は今日までを返す。メモリを抑えるため 1 日ずつ読み、JSON にできない行は飛ばす(飛ばした数は監査ログの skipped)。REST のレスポンスは JSON に限られるため、NDJSON は ndjson フィールドの文字列で返す。データが無い月も 200(count は 0)。1 年分は 12 回の呼び出しになる。レート制限(1 分あたり 20 回)を超えて 429 になったら、Retry-After の秒数だけ待ってから呼ぶ。
毎日の差分は ?hall=island&year=2026&since=2026-10-06 のように、前回の取得時点で取り込みが終わっていた日(/coverage の latest_day)の翌日を since に渡す。今日など取り込み途中の日を返したときは、その日の行と samai_step が途中の値なので、次回もう一度取り直す。since の月から始まり、月をまたぐときは next_cursor を since と一緒に渡して続きを取る(since より後の月は月全体)。年をまたぐときは新しい year の 1 月から since なしで取る。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
hall が無い・対象ホール以外 | 400 | RecommendRestCopy::HALL_INVALID |
year が 2020〜今年の整数でない | 400 | RecommendRestCopy::YEAR_INVALID |
cursor が YYYY-MM でない・year と年が違う・月が不正 | 400 | RecommendRestCopy::CURSOR_INVALID |
since が Y-m-d の実在する日でない・year と年が違う・今日より後 | 400 | RecommendRestCopy::SINCE_INVALID |
cursor が since の月より前 | 400 | RecommendRestCopy::CURSOR_BEFORE_SINCE |
| DB の読み込みに失敗した | 503 | RecommendRestCopy::READ_FAILED |
監査ログ
BotRestAuditLogger で error_log に 1 行 JSON(接頭辞 [bot-rest-audit])を書く。記録するのはルート・ユーザー ID・hall・month・行数(count)・飛ばした行数(skipped)だけ。入力検証で 400、DB の失敗で 503 になったリクエストは記録しない。
DB 読み込みの失敗(API-009-3〜6 共通)
$wpdb->last_error が空でなければ 0 件と区別して 503 にし、error_log に 1 行(RecommendRestCopy::LOG_ERROR_FORMAT)残す。db2023 のインデックスは UNIQUE (DAY, dainum, hall) だけなので、最初の日・最新日は ORDER BY DAY ASC/DESC LIMIT 1 でインデックスの端から読み、それ以外のクエリは必ず DAY の範囲で絞る。
API-009-7・8・9 共通の入力
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | はい | island / espasu / bigapple | ホール |
date_from | はい | Y-m-d | 開始日(含む) |
date_to | はい | Y-m-d | 終了日(含む) |
出力はどれも {success: true, hall, date_from, date_to, count, items}(count は items の件数)。HTTP ステータスは 200。
| 条件 | HTTP | message |
|---|---|---|
hall が無い・対象ホール以外 | 400 | RecommendRestCopy::HALL_INVALID |
日付が無い・Y-m-d でない・存在しない日付・date_from が後 | 400 | RecommendRestCopy::DATE_RANGE_INVALID |
| 期間が上限(各 API の項)を超える | 400 | RecommendRestCopy::DATE_RANGE_TOO_LONG |
| DB の読み込みに失敗した | 503 | RecommendRestCopy::READ_FAILED |
機種別サマリ・〇〇の日マスタ・日別リンク・台数変化・日別記事の既存 Repository は DB の失敗を空配列にして返すため、RecommendSummaryService は Repository を呼ぶたびに DatabaseErrorProbeInterface($wpdb->last_error)で失敗を確かめ、0 件と区別して 503 にする。失敗したクエリの結果は BaseRepository がキャッシュしない。
API-009-7 GET /kishu-daily
日別記事の機種別サマリ(db_daily_article_kishu_single_day_summary)を機種・日ごとに返す。期間は最大 92 日(RecommendSummaryRestController::KISHU_DAILY_MAX_DAYS)。サマリを集計していない日は行が無い。
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
kishu_ids | いいえ | 機種 ID(正の整数)のカンマ区切り、50 件以内 | 指定した機種だけ返す。省略時はすべての機種 |
| field | 型 | 説明 |
|---|---|---|
items[].date | string | 日付 |
items[].kishu_id | int | 機種 ID |
items[].kishu | string | 機種名 |
items[].count | int | 台数 |
items[].total_samai | int | 総差枚 |
items[].avg_samai | float | 平均差枚(小数 1 桁) |
items[].avg_g | float | 平均 G 数(小数 1 桁) |
items[].total_samai_rank | int | その日のホール内の総差枚順位 |
items[].avg_samai_rank | int | その日のホール内の平均差枚順位 |
並びは日付の昇順、同じ日は total_samai_rank の昇順。kishu_ids が不正なときは 400(RecommendRestCopy::KISHU_IDS_INVALID)。
すべての機種を 92 日分取ると 1 万行を超える(数 MB)ことがある。必要な機種だけ kishu_ids で絞るか、期間を短くして呼ぶ。
API-009-8 GET /end-number
台データがある日ごとに、末尾 0〜9 とゾロ目の集計を返す。期間は最大 31 日(RecommendSummaryRestController::END_NUMBER_MAX_DAYS)。数え方は日別記事の末尾データと同じで、末尾は台番号の最後の数字(数字でなければ 0 として数える)、ゾロ目は台番号の最後の 2 文字(バイト単位)が同じ台(通常は下 2 桁が同じ数字。英字など数字以外でも同じならゾロ目になる。末尾の集計にも入る)。db2023 は 1 回のクエリで「日付 × 末尾 × ゾロ目かどうか」ごとの台数・差枚と G 数の合計・勝ち台数を SQL で集計して受け取り(RecommendUnitDataRepositoryInterface::aggregate_end_digits。機種マスタとは JOIN しない)、PHP では日ごとの末尾 0〜9 とゾロ目の合計にまとめるだけにする(台ごとの行は PHP に読み込まない)。
| field | 型 | 説明 |
|---|---|---|
items[].date | string | 日付(台データが無い日は含まない) |
items[].units | int | その日の台数 |
items[].digits[].digit | string | 0〜9 または zoro(この順で 11 件) |
items[].digits[].units | int | 台数 |
items[].digits[].avg_samai | float|null | 平均差枚(小数 1 桁)。台が無いときは null |
items[].digits[].avg_g | float|null | 平均 G 数(小数 1 桁) |
items[].digits[].win_units | int | 勝ち台数(差枚がプラスの台) |
API-009-9 GET /calendar
期間内のすべての日について、曜日・〇〇の日・ホールのイベント・機種の台数変化(入替)を返す。期間は最大 92 日(RecommendSummaryRestController::CALENDAR_MAX_DAYS)で、先の日付も指定できる。
| field | 型 | 説明 |
|---|---|---|
items[].date | string | 日付 |
items[].weekday | string | sun〜sat |
items[].weekday_ja | string | 日〜土 |
items[].what_days | array | 〇〇の日マスタ(db_what_day_master の表示中のもの)で日付が当てはまる名前 |
items[].espasu_what_days | array | エスパスの日別記事に手入力で掲載した〇〇の日(考察日ごと。公開済みの記事だけ)。hall に関係なくエスパスの値 |
items[].events | array | 日別リンク(db_link_day.event)に登録したそのホールのイベント名 |
items[].replacements[].kishu | string | 機種名 |
items[].replacements[].count_delta | int | 前日からの台数の増減(db_daily_article_kishu_count_delta) |
items[].replacements[].is_new_kishu | bool | 新台情報(db_new_machine_info)の導入日が date_to の前月 1 日〜当月末にある機種か。期間内のどの日の行も date_to の月で判定する |
〇〇の日とイベントは有料記事の AI ツール(fetch_what_days_by_period / fetch_event_info)と同じデータ。入替は台データを取り込んだ日にしか無いので、先の日付は空になる。
what_days は〇〇の日マスタ(ADM-004)だけを見る。マスタに表示中の行が 1 件も無いと全期間で空になる(エラーにはしない)。エスパスの日別記事に手入力した〇〇の日は espasu_what_days で返す。events は日別リンクに今登録されているイベント名で、日別リンクは登録した日時を持たない(事前に告知されたものか、あとから付いたものかは区別できない)。
権限・nonce・レート制限
全エンドポイント共通。RecommendRestHandler + WordPressRecommendPermissionChecker。Handler がルートごとに action(read / export / write)を付け、Checker は action に応じて Capability とレート制限を変える。action が無い・不明なときは 403。
| 項目 | GET(API-009-1・3〜5・7〜9) | GET /export(API-009-6) | POST(API-009-2) |
|---|---|---|---|
action | read | export | write |
| Capability | read_recommend_data | read_recommend_data | write_recommend_draft |
| レート制限 | ユーザー単位で 1 分あたり 120 回 | ユーザー単位で 1 分あたり 20 回 | ユーザー単位で 1 分あたり 20 回 |
| カウンタ不可時 | 通す(fail-open) | 拒否する(fail-closed、429) | 拒否する(fail-closed、429) |
| 項目 | 内容 |
|---|---|
| 匿名アクセス | 403 |
| nonce | 検証しない(アプリケーションパスワード前提)。Cookie 認証で呼ぶ場合の REST nonce は WordPress コア側の挙動に従う |
| レート制限 | BotRestRateLimiter(transient、60 秒の固定ウィンドウ。最初の呼び出しから 60 秒)。バケットは recommend:read / recommend:export / recommend:write。超過時は 429 |
| 応答ヘッダー | 数えた呼び出しには X-RateLimit-Limit(上限)・X-RateLimit-Remaining(残り回数)・X-RateLimit-Reset(ウィンドウが切り替わる時刻の Unix タイムスタンプ)を付ける。429 には Retry-After(秒)も付ける。Local バイパス時・未ログイン時・カウンタを保存できなかったとき(read の fail-open で通したときと、export・write の fail-closed で 429 にしたとき)は付けない |
| ログ | ログイン済みユーザーの 403 と、429 のときだけ error_log に 1 行(ステータス・user_id・ルート)。未ログインの 403 と許可したアクセスは記録しない |
| Local バイパス | API-001 の WordPressPermissionChecker と同じ条件(レート制限もかけない) |
WordPress は応答の Allow ヘッダーを作るため、同じリクエストで同じルートの全ハンドラーの permission_callback をもう一度呼ぶ(rest_send_allow_header)。WordPressRestApiAdapter が 1 リクエストにつき結果を 1 回だけ求めて使い回し、リクエストのメソッドを受け付けないハンドラー(GET /drafts のときの POST など)ではドメイン側を呼ばないため、レート制限は 1 リクエスト 1 回だけ数える(Issue #3918。以前は 1 回の呼び出しで 2 回数え、GET /drafts で recommend:write も減っていた)。このため応答の Allow にはリクエストのメソッドだけが載り(GET /drafts は Allow: GET)、OPTIONS の応答には Allow を付けない。レート制限を数えずに他のメソッドの可否を調べる手段が無いための意図した挙動。
おすすめ台担当ロールは wp-admin を開けない(MemberAdminAccessGuardHooks がサイトトップへリダイレクトし、管理バーも出さない)。記事の Capability は持たない。
WordPress コア経由の書き込みの遮断
recommend_picker を持ち、他には REST 用 bot ロールしか持たないユーザーには BotWriteGuardHooks で、X告知担当(API-007)と同じ制限をかける。違いは REST で通す書き込みが POST /recommend/v1/drafts だけであること。エラーコードは recommend_write_forbidden(RecommendRestCopy::WRITE_FORBIDDEN)/ recommend_xmlrpc_forbidden(RecommendRestCopy::XMLRPC_FORBIDDEN)。