Skip to content

API-001-19 日別記事bot編集 REST ​

← API-一覧

概要 ​

日別記事のブロックエディタで編集している項目(考察日・ホール・イベント・〇〇の日・ホール別考察・絵文字)を、管理画面を開かずに日別記事編集bot(daily_article_editor_bot)がアプリケーションパスワードで読み書きするための REST。名前空間は daily-article/v1(API-001 と同じ)。

IDパスメソッド内容
API-001-19/articlesGET考察日の範囲で日別記事を一覧する
API-001-20/articles/{id}GET1 記事の編集項目を取得する
API-001-21/articles/{id}/fieldsPOST編集項目を部分更新する
API-001-22/articles/{id}/trashPOST未公開の日別記事をゴミ箱へ移す
API-001-23/eventsGETホールのイベント候補を取得する
API-001-24/eventsPOSTイベントマスタへ登録する(同名は既存 ID)
API-001-25/what-day-masterPOST〇〇の日マスタへ登録する(同名は既存 ID)
API-001-26/birthdaysGET前後 N 日の誕生日とホール別の台数を返す
API-001-27/birthdays/searchGET声優名・キャラ名で誕生日を検索する

bot が触れるのは日別記事の下表の項目(DailyArticleFieldsUpdateServiceInterface::ALLOWED_KEYS)だけ。本文・タイトル・ステータス・公開日時・投稿者・ほかの投稿タイプは変更できない。日別記事の show_in_rest は false のままで、WordPress コアの /wp/v2/ からは扱えない。

API-001-26 / 27 は考察の材料にする誕生日データの読み取り専用ルート(Issue #3995)。誕生日マスタの保守(紐付けの追加など)は誕生日紐付け担当の API-008 で行い、本 API からは書き込めない。

〇〇の日 の候補と機種名の候補は既存の API-001-5・API-001-8 を使う(同じ権限で呼べる)。

編集項目 ​

値の正規化(サニタイズ・保存形式)は管理画面の DailyArticleBlockEditorSaver と同じ DailyArticleFieldNormalizer で行う。管理画面は不正値を読み飛ばすが、本 API は 1 つでも不正があれば何も書かずに 400 を返す。

キー型(入力)検証空にしたとき
kousatsu_datestring YYYY-MM-DD実在する日付。変えるとき、保存済みの espasu_what_day_manual が新しい考察日の候補に無ければ espasu_what_day_manual も送らないと 400(黙って消さない)空にできない
hallsstring[](カンマ区切り文字列も可)日別記事で使えるホール(HallEnum::is_available_for_daily_article())。重複は 1 つにまとめるmeta を削除
daily_article_events{ ホール: イベントID[] }ホールは記事の halls(同じリクエストで halls も送ればその値)に含まれること。ID はイベントマスタに存在し、ホールが一致すること。1 ホール 20 件までmeta を削除
espasu_what_day_manualstring[]考察日の「〇〇の日」候補(API-001-5 と同じ WhatDayMasterRepository::select_visible_by_mmdd)にある名前だけ空配列で保存
island_pre 等 6 項目string(HTML)文字列であること。管理画面と同じ sanitize_post_field のあと、unfiltered_html を持たないユーザー(日別記事編集bot 等)は wp_kses_post を通す空文字で保存
island_emoji 等 3 項目string文字列・500 文字以内。管理画面と同じ EmojiTextSanitizer空文字で保存

ホール別考察は island_pre / island_after / espasu_pre / espasu_after / bigapple_pre / bigapple_after、絵文字は island_emoji / espasu_emoji / bigapple_emoji。

イベントは ID だけを受け取り、保存する名前はイベントマスタから引き直す(保存形式は管理画面と同じ { ホール: [ { id, name } ] })。

ホール別考察の KSES は管理画面の保存(DailyArticleBlockEditorSaver)にも同じくかかる。WordPress コアが unfiltered_html を持たないユーザーの post_content を保存時に KSES へ通すのと同じ扱いで、そうしたユーザー(編集者権限を絞ったアカウント・マルチサイトの管理者以外など)が管理画面で保存すると、6 項目に既に入っている <iframe>・<script> 等の許可されていないタグや属性はその保存で消える。unfiltered_html を持つ管理者の保存では変わらない。wp_kses は HTML コメントを残すため、ブロックコメント(<!-- wp:... -->)・data-* 属性・エディタで使うブロック(段落・画像・埋め込み・ショートコード・コード・グループ・Cocoon 白box)のマークアップは変わらない。ブロックエディタの文字色(<mark style="background-color:rgba(...)">)が消えないよう、KSES の間だけ color / background-color の rgb() / rgba() 値を許可する(DailyArticleFieldNormalizer::allow_rgb_color_css)。

公開済み記事の考察日の検証 ​

公開済み記事は保存のたびに ValidationHooks(save_post 20 / 25)が考察日を検証し、未入力・日付として読めない・別の公開済み日別記事と重複のいずれかなら下書きに戻す。本 API は保存前に同じ判定で止める。

条件HTTPmessage
保存済みの考察日が DailyArticleDateUtil::normalize_date() で読めない(kousatsu_date を送っていない)400FIELDS_INVALID(errors に KOUSATSU_DATE_STORED_INVALID)
保存後の考察日が別の公開済み日別記事と重複する(考察日を変えないときも確認する。判定は DailyArticlePublishedDateDuplicateFinder を ValidationHooks と共有)409KOUSATSU_DATE_DUPLICATE
事前確認をすり抜けて保存後にステータスが変わった(項目は保存済み。下の「保存後に下書きに戻された場合」)409STATUS_REVERTED(saved: true)

旧形式(2026-1-5 など)でも normalize_date() で読める考察日なら、ほかの項目だけを更新できる。

重複の事前確認は、公開済み記事を実際に保存するときだけ行う(値が変わらず保存しないリクエストでは ValidationHooks も動かないため確認しない)。考察日を変えない保存でも ValidationHooks は毎回重複を検証するので、考察日の変更有無では省かない。

保存後に下書きに戻された場合(STATUS_REVERTED) ​

事前確認と保存の間に別の記事が同じ考察日で公開された場合など、まれに事前確認をすり抜けることがある。このとき送った項目は保存済みで、記事は ValidationHooks の考察日の検証(重複・不正な日付)によって下書きに戻っている。同じリクエストを再送すると、値は保存済みなので changed_keys が空の 200 が返り、成功したように見えるが記事は下書きのまま。

  • bot は再送しない。人が管理画面で考察日を確認・修正(重複の解消など)してから公開し直す
  • 409 のボディに次を返す。監査ログにも outcome: saved_but_reverted・before_status / after_status・文字数を残す
field型説明
successboolfalse
messagestringDailyArticleBotRestCopy::STATUS_REVERTED
savedbooltrue(項目は保存済み)
idint記事 ID
statusstring今のステータス(通常 draft)
previous_statusstring保存前のステータス(publish)
changed_keysstring[]保存したキー
modified_gmtstring保存後の modified_gmt

保存時の副作用 ​

管理画面で保存したときと同じ処理が走るよう、meta は wp_update_post( [ 'ID' => $id ] ) が発火する save_post_daily_article / save_post(どちらも優先度 10)の中で書く。両方に載せ、先に来た方で 1 度だけ書く(保存後のフックが中で wp_update_post を呼んで再発火しても書き直さない)。

WordPress は save_post_daily_article を save_post より先に発火するため、通常は save_post のどのコールバックよりも前に meta を書く。管理画面の Saver は save_post 10 で書くので、ここだけ順序が違う。今回の meta を読む save_post 10 以下のフックは無いため結果は変わらないが、save_post の低い優先度に meta を読むフックを足すときは、bot 経由では meta が先に書かれている前提で作ること。

フック処理
pre_post_update変更前の考察日を控える(旧日付のキャッシュ無効化用)。入れ子の更新では上書きしない
save_post_daily_article 10(外されていれば save_post 10)本 API が meta を書く。絵文字は専用テーブル(daily_article_emoji)へ同期する
meta 更新フック考察テーブルの同期、イベント画像の紐付け予約
save_post 20 / 25考察日からタイトルを作り直す、公開済み記事の考察日の検証
save_post 99 / 100link_day の同期、キャッシュ無効化

タイトルが変わると save_post 20 の中で入れ子の wp_update_post が走る。pre_post_update の控えは最初の値(変更前の考察日)のまま残り、入れ子の save_post 100 で旧日付・新日付の両方(それぞれ前後日を含む)のキャッシュが消える。外側の save_post 100 は控えが無いため、新しい考察日のキャッシュだけを消し直す。

管理画面の Saver は $_POST['_wpnonce'] が無いため何もしない(二重に書かない)。送った値がすべて今の値と同じときは保存しない(changed_keys は空、modified_gmt は変わらない)。

フックで書けなかった場合(フォールバック) ​

wp_update_post は成功した(modified_gmt が進んだ)のに、どちらのフックでも書き込みが呼ばれなかったとき(他のフックが書き込み用のフックを外した等)は、wp_update_post の直後に meta を書いて 200 を返す。投稿だけ更新されて meta が古いまま残る状態にはしない。meta 更新フック(考察テーブルの同期・イベント画像の紐付け予約)はこの書き込みで動く。

1 回目の wp_update_post の save_post 20 以降は古い meta のまま終わっているため、save_post が発火していた場合は wp_update_post をもう一度呼び、タイトル生成・検証・キャッシュ無効化を新しい meta で走らせ直す(旧考察日のキャッシュは 1 回目、新考察日のキャッシュは 2 回目で消える)。返す modified_gmt は 2 回目の後の値。

1 回目の検証(ValidationHooks)は古い meta で行われるため、古い考察日が別の公開済み記事と重複している状態を bot が考察日を変えて直そうとすると、新しい値が正しくても 1 回目で下書きに戻り 409(STATUS_REVERTED)になる(監査ログには save_path: fallback が残る)。

save_post 自体が発火していなかった場合(side_effects_rerun = skipped)と、2 回目が失敗した場合(side_effects_rerun = wp_error)は、次の処理が古い meta のままになる。

  • 考察日を変えてもタイトルが作り直されない
  • 新しい考察日とその前後日のテンプレートキャッシュが消えない(save_post が発火していなければ、考察本文の Transient も消えない)
  • 公開済み記事の考察日の検証(ValidationHooks)が新しい考察日で行われていない(事前の重複確認は済んでいる)

監査ログに "save_path":"fallback" かつ side_effects_rerun が done 以外の行が出たら、その記事を管理画面で開いて更新し直す(タイトル・キャッシュ・検証が管理画面保存として走る)。

監査ログの save_path で書いた経路を区別する。typed_hook_fired / save_post_fired は原因の切り分け用で、wp_update_post の前後で did_action() の回数が増えたかを記録する。did_action() はリクエスト全体の回数のため、保存後のフックが中で呼ぶ wp_update_post や別投稿の保存でも増える(「対象記事のフックが発火した」ことまでは保証しない)。

save_path意味
hook_typedsave_post_daily_article の中で書いた(通常)
hook_save_postsave_post_daily_article では書かれず、save_post の中で書いた
fallbackどちらでも書かれず、wp_update_post の直後に書いた

wp_update_post が WP_Error を返したときは 500(UPDATE_FAILED、reason: wp_error)で、meta は書かない。エラーコード(get_error_code())は監査ログの error_code にそのまま出す。レスポンスの error_code には、wp_insert_post が返すコアのコード(DailyArticleBotRestController::PUBLIC_WP_ERROR_CODES: invalid_post / invalid_page_template / invalid_date / empty_content / db_update_error)だけをそのまま出し、それ以外(コアの将来の変更などで想定外のコードが来た場合)は other にする。

wp_insert_post は投稿行を UPDATE した後、save_post を発火する前に page_template(ID だけ渡しても get_post( ARRAY_A ) から保存済みの _wp_page_template が引き継がれる)をテーマのテンプレート一覧と照合する。テーマは 1 階層下までしかテンプレートを探さないため、TemplateHooks が保存する myCustom/myTemplate/single-daily-article-template.php と旧値 myTemplate/single-daily-article-template.php は PageTemplateRegistry の対応表に載せ、PageTemplateRegistryHooks(theme_templates フィルタ)で一覧に加えている。外すと invalid_page_template で modified だけ進む 500 になる。一覧が空でなくなると編集画面にテンプレート選択(ページ属性)が出るため、pageparentdiv メタボックスは外している(値は保存時に auto_set_template が上書きする)。

API-001-19 GET /articles ​

入力(リクエスト) ​

param必須型・制約説明
date_fromはいYYYY-MM-DD考察日の開始(含む)
date_toはいYYYY-MM-DD(date_from 以上・開始から 31 日以内)考察日の終了(含む)
statusいいえpublish / draft / pending / future のカンマ区切りか配列絞り込むステータス(省略時は 4 つすべて)

出力(レスポンス) ​

field型説明
successbooltrue
itemsarray{ id, title, kousatsu_date, status, author_id, author_name, modified_gmt }[]。考察日の昇順、最大 200 件

HTTP ステータス: 200

API-001-20 GET /articles/{id} ​

出力(レスポンス) ​

field型説明
successbooltrue
articleobjectid / title / status / modified_gmt と編集項目 13 個。halls と espasu_what_day_manual は配列、daily_article_events は { ホール: [ { id, name } ] }(無ければ {})

HTTP ステータス: 200

API-001-21 POST /articles/{id}/fields ​

入力(リクエスト) ​

JSON オブジェクトのボディ。送ったキーだけ更新する。

param必須型・制約説明
編集項目1 つ以上上の「編集項目」許可リスト外のキーが 1 つでもあれば 400(unknown_keys)で何も更新しない
expected_modifiedいいえYYYY-MM-DD HH:MM:SS(T・末尾 Z 可)最後に取得した modified_gmt。現在の値と違えば 409 で何も更新しない(ほかの人の編集を上書きしないため)

出力(レスポンス) ​

field型説明
successbooltrue
messagestringDailyArticleBotRestCopy::UPDATE_SUCCESS
idint記事 ID
changed_keysstring[]今の値と比べて値が変わったキー(送っても同じ値のキーは含まない)
modified_gmtstring更新後の modified_gmt(次の更新で使う)

HTTP ステータス: 200

保存に失敗したとき(500)のボディ:

field型説明
successboolfalse
messagestringDailyArticleBotRestCopy::UPDATE_FAILED
reasonstring失敗の種類。wp_error(wp_update_post が WP_Error を返した)/ unknown(想定外)
error_codestringreason: wp_error のときだけ。WordPress コアのエラーコード、またはそれ以外を表す other
modified_gmtstring失敗後の現在の modified_gmt。再送するときはこの値を expected_modified に使う

API-001-22 POST /articles/{id}/trash ​

下書き(draft / auto-draft)と承認待ち(pending)の日別記事だけをゴミ箱へ移す。公開済み・予約済みは 409。完全削除はしない(EMPTY_TRASH_DAYS が 0 でゴミ箱が無効な環境では 409)。

出力(レスポンス) ​

field型説明
successbooltrue
messagestringDailyArticleBotRestCopy::TRASH_SUCCESS
idint記事 ID

HTTP ステータス: 200

API-001-23 GET /events ​

入力(リクエスト) ​

param必須型・制約説明
hallはい日別記事で使えるホール対象ホール

出力(レスポンス) ​

field型説明
successbooltrue
hallstringホールキー
itemsarray{ id, name }[](ブロックエディタのイベント候補と同じ一覧)

HTTP ステータス: 200

API-001-24 POST /events ​

入力(リクエスト) ​

param必須型・制約説明
hallはい日別記事で使えるホール登録先ホール
nameはい1〜255 文字(前後の空白は除く)イベント名(sanitize_text_field)

出力(レスポンス) ​

field型説明
successbooltrue
messagestringDailyArticleBotRestCopy::EVENT_REGISTERED / EVENT_ALREADY_EXISTS
idintイベント ID
hallstringホールキー
namestring保存したイベント名
createdbool新規登録なら true。同じホールに同名が既にあれば false(既存 ID を返し、登録しない)

HTTP ステータス: 新規登録 201 / 既存 200

API-001-25 POST /what-day-master ​

〇〇の日 の候補(API-001-5)に無い日を、bot が〇〇の日マスタ(db_what_day_master)へ足すためのルート。登録後に API-001-5 を呼び直せば候補に出る(is_show が true のとき)。

入力(リクエスト) ​

param必須型・制約説明
nameはい1〜255 文字(前後の空白は除く)〇〇の日の名前(sanitize_text_field)
mmddはいMMDD の整数(101〜1231 の実在日。2 月 29 日は可)。数字だけの文字列も受け付ける月日(例: 10 月 4 日 → 1004)
is_showいいえtrue / false(1 / 0、"true" / "false" も可)。既定 true投稿の選択肢(API-001-5)に出すか

同名が既にあるとき ​

既存行はテーブルの一意キー(uk_name)と同じ照合順序で探す(大文字・小文字、全角・半角、濁点・半濁点の違いは同名とみなされることがある)。bot がほかの日付の候補を壊さないよう、既存行は表記まで一致したときに次の範囲だけ変える。更新は条件付きの 1 文で行い、変更後の行を読み直して結果を返す。

既存行の状態結果(outcome)HTTP既存行への変更
表記が違う(例: ハチの日 / パチの日)name_mismatch409なし(別名で登録したいときは管理画面で人が行う)
同じ月日・表示中exists200なし(is_show: false を送っても非表示にはしない)
同じ月日・非表示で is_show が trueupdated200表示に切り替える
月日が未設定updated200月日を入れる(is_show: true なら表示にも切り替える。表示中の行は is_show: false でも表示のまま)
別の月日mmdd_conflict409なし(月日の付け替えは〇〇の日マスタ管理画面で人が行う)

名前・年月日(yyyymmdd)の変更と削除はこのルートではできない。

出力(レスポンス) ​

field型説明
successbooltrue
messagestringDailyArticleBotRestCopy::WHAT_DAY_MASTER_REGISTERED / WHAT_DAY_MASTER_UPDATED / WHAT_DAY_MASTER_ALREADY_EXISTS
idint〇〇の日マスタ ID
namestring登録済みの名前
mmddint登録済みの月日
is_showbool登録済みの表示フラグ
createdbool新規登録なら true
updatedbool同名の既存行の表示・月日を変えたなら true

HTTP ステータス: 新規登録 201 / 既存(変更あり・なし) 200

409 のボディは { success: false, message, id, name, mmdd }(message は別の月日なら WHAT_DAY_MASTER_MMDD_CONFLICT、表記違いなら WHAT_DAY_MASTER_NAME_MISMATCH。name / mmdd は登録済みの値で、mmdd は未設定なら null)。

API-001-26 GET /birthdays ​

基準日の前後 N 日の誕生日を、紐付いた機種とホール別の設置台数つきで返す。Controller は DailyArticleBirthdayRestController、集計は DailyArticleBirthdayService。

入力(リクエスト) ​

param必須型・制約説明
dateはいYYYY-MM-DD基準日
daysいいえ0〜7 の整数。既定 3基準日の前後 N 日(両端を含む。最大 15 日分)
hallsいいえ日別記事で使えるホール(island / espasu / bigapple)のカンマ区切り。既定 island,espasu台数を出すホール。重複は 1 つにまとめる
include_last_yearいいえ1 / 0(true / false も可)。既定 0各機種に去年同日の台数・差枚(last_year)を付ける

処理 ​

  • 範囲の各日の月日で誕生日(db_birthday)を引く。月・年をまたいでよい。閏年でない年の 2/28 には 2/29 の誕生日も含める(誕生日ピックアップと同じ。Issue #1337)
  • 紐付いた機種は db_birthday_kishu(作品名 ↔ 機種)から引く。機種名が空・- の紐付けは除く。紐付けが無い誕生日も kishu: [] で返す
  • 設置台数: ホールごとに「date 以前でデータがある最新日(最大 7 日さかのぼる)」を台数の基準日とし、その日の台数を返す。データがある日とは、summary(db_daily_article_kishu_single_day_summary)か台別(db2023)にそのホールの行がある日
    • 台数は summary の count を優先し、summary に無いホール × 機種は台別の行数で埋める(誕生日ピックアップ BirthDayMachineResultService と同じ規則。Issue #2092)
    • ピックアップは「記事日〜2 日前・全ホール共通の基準日」だが、bot は取込前の日付で呼ぶことがあるため、基準日をホール別にし、7 日まで広げている。基準日が同じなら台数はピックアップと一致する
  • 去年の結果(include_last_year=1): 各誕生日の date の 1 年前(2/29 は 2/28)の、その日ちょうどの値(基準日をずらさない)。summary を優先し、無ければ台別(行数・samai の合計・平均)。誕生日マスタ・紐付けは今の内容で引く(去年の紐付けの履歴は持っていない)
  • BB/RB・回転数は読まない・返さない
  • 月日ごとの誕生日 × 機種の一覧はオブジェクトキャッシュに最大 1 時間残る(公開ページの誕生日ピックアップと共通)。機種の紐付け(db_birthday_kishu)を変えるとキャッシュは消えるが、誕生日マスタ(db_birthday)・作品名の追加・修正は最大 1 時間遅れて反映される

出力(レスポンス) ​

field型説明
successbooltrue
datestring基準日
daysint前後の日数
date_from / date_tostring範囲の両端
hallsstring[]台数を出したホール
count_reference_datesobject{ ホール: 台数の基準日(YYYY-MM-DD)| null }。7 日以内にデータが無いホールは null
itemsarray誕生日。並びは date 昇順 → birthday_id 昇順(下表)

items[]:

field型説明
datestring範囲内の日(閏年でない年の 2/29 生まれは 2/28 に入る)
offsetint基準日からの日数(-N〜N)
birthday_idint | nulldb_birthday.id(今の SQL では null にならない)
divistringキャラ誕 / 声優誕
actorstring声優名(divi の (声誕) 以降)。キャラ誕は空文字
charastringキャラ名(声優誕の行は、その声優が演じたキャラ)
title_idint | null作品名マスタ ID(作品名マスタと INNER JOIN するため、今は null にならない)
titlestring作品名
kishuarray{ kishu_id, kishu_name, halls: { ホール: { count } }, last_year? }[]
  • count: 基準日にそのホールのデータはあるが機種が無ければ 0。そのホールに基準日が無ければ null
  • last_year(include_last_year=1 のときだけ): { date, halls: { ホール: { count, total_samai, avg_samai } | null } }。その日そのホールに該当機種のデータが無ければ null。avg_samai は小数 1 桁
json
{
  "success": true,
  "date": "2026-10-10",
  "days": 3,
  "date_from": "2026-10-07",
  "date_to": "2026-10-13",
  "halls": ["island", "espasu"],
  "count_reference_dates": { "island": "2026-10-09", "espasu": "2026-10-09" },
  "items": [
    {
      "date": "2026-10-08",
      "offset": -2,
      "birthday_id": 123,
      "divi": "声優誕",
      "actor": "声優名",
      "chara": "キャラ名",
      "title_id": 45,
      "title": "作品名",
      "kishu": [
        {
          "kishu_id": 678,
          "kishu_name": "機種名",
          "halls": { "island": { "count": 3 }, "espasu": { "count": 0 } },
          "last_year": {
            "date": "2025-10-08",
            "halls": { "island": { "count": 3, "total_samai": 1234, "avg_samai": 411.3 }, "espasu": null }
          }
        }
      ]
    }
  ]
}

HTTP ステータス: 200

声優名・キャラ名の部分一致で誕生日を返す。台数は返さない(必要なら API-001-26 を date 指定で呼ぶ)。

入力(リクエスト) ​

param必須型・制約説明
qはい1〜50 文字(sanitize_text_field 後、前後の半角・全角空白を除く)検索語(部分一致)
fieldいいえany / actor / chara。既定 anyactor は divi の (声誕) 以降、chara は chara 列、any は両方
limitいいえ1〜50 の整数。既定 20誕生日の件数上限(紐付いた機種の数ではない)

半角・全角スペースは検索語・列の両方から除いて比べる(「花澤 香菜」と「花澤香菜」を同じにする)。検索結果も API-001-26 と同じくオブジェクトキャッシュに最大 1 時間残る。機種の紐付けを変えると消えるが、誕生日マスタ(db_birthday)・作品名の追加・修正では消えないため、追加したばかりの声優・キャラは最大 1 時間見つからないことがある。

出力(レスポンス) ​

field型説明
successbooltrue
qstring前後の空白を除いた検索語
fieldstring検索した項目
countintitems の件数
itemsarray{ birthday_id, month, day, divi, actor, chara, title_id, title, kishu: [ { kishu_id, kishu_name } ] }[]。並びは month → day → birthday_id
  • 声優で引いたとき、声優誕の行の chara が演じたキャラ、month / day が声優の誕生日
  • キャラで引いたときは、キャラ誕の行(キャラの誕生日)と声優誕の行(演じた声優とその誕生日)の両方が返る
  • 機種名が空・- の紐付けは kishu に含めない

HTTP ステータス: 200

失敗・エラー条件 ​

失敗時のボディは { success: false, message }。message は DailyArticleBotRestCopy の固定文言で、入力値や例外メッセージは載せない。

条件HTTPmessage
id が正の整数でない400ARTICLE_ID_INVALID
date_from / date_to 不正400DATE_RANGE_INVALID
一覧の範囲が 31 日を超える400DATE_RANGE_TOO_LONG
status 不正400STATUS_INVALID
ボディが JSON オブジェクトでない400BODY_INVALID
更新する項目が無い400FIELDS_EMPTY
許可リスト外のキーがある400FIELDS_UNKNOWN(unknown_keys 付き)
項目の値が不正400FIELDS_INVALID(errors に項目ごとの固定文言)
expected_modified の形式不正400EXPECTED_MODIFIED_INVALID
hall 不正400HALL_INVALID
name 不正400EVENT_NAME_INVALID / WHAT_DAY_MASTER_NAME_INVALID(API-001-25)
mmdd 不正(API-001-25)400WHAT_DAY_MASTER_MMDD_INVALID
is_show 不正(API-001-25)400WHAT_DAY_MASTER_IS_SHOW_INVALID
date / days / halls / include_last_year 不正(API-001-26)400BIRTHDAY_DATE_INVALID / BIRTHDAY_DAYS_INVALID / BIRTHDAY_HALLS_INVALID / BIRTHDAY_INCLUDE_LAST_YEAR_INVALID
q / field / limit 不正(API-001-27)400BIRTHDAY_QUERY_INVALID / BIRTHDAY_FIELD_INVALID / BIRTHDAY_LIMIT_INVALID
未ログイン(Cookie 認証で nonce が無い場合を含む)401LOGIN_REQUIRED
権限が無い403WordPress REST 標準の 403
記事が無い・日別記事でない・ゴミ箱にある404ARTICLE_NOT_FOUND
expected_modified が現在の modified_gmt と違う409EXPECTED_MODIFIED_MISMATCH
公開済み記事の考察日が、別の公開済み記事と同じ日になる409KOUSATSU_DATE_DUPLICATE
保存後に公開済み記事が下書きに戻された(項目は保存済み)409STATUS_REVERTED(saved: true。再送しない)
ゴミ箱へ移せないステータス409TRASH_STATUS_CONFLICT
ゴミ箱が無効(EMPTY_TRASH_DAYS が 0)409TRASH_DISABLED
同名の〇〇の日が別の月日で登録済み409WHAT_DAY_MASTER_MMDD_CONFLICT
照合順序で同名だが表記が違う〇〇の日が登録済み409WHAT_DAY_MASTER_NAME_MISMATCH
レート制限を超えた429Messages::RATE_LIMIT_EXCEEDED
更新・登録・ゴミ箱移動に失敗500UPDATE_FAILED / EVENT_REGISTER_FAILED / WHAT_DAY_MASTER_REGISTER_FAILED / TRASH_FAILED
DB の読み取りに失敗(API-001-26 / 27。0 件と区別するため DatabaseErrorProbeInterface で判定)503BIRTHDAY_READ_FAILED(例外メッセージは error_log にだけ出す)

レート制限 ​

ユーザーごとの固定ウィンドウ(60 秒)。BotRestRateLimiter(transient)で数える。

対象上限カウンタを保存できないとき
項目更新(API-001-21)60 回 / 分拒否(429)
イベント登録(API-001-24)20 回 / 分拒否(429)
〇〇の日マスタ登録(API-001-25)20 回 / 分拒否(429)
ゴミ箱移動(API-001-22)20 回 / 分拒否(429)
読み取り(API-001-19 / 20 / 23 / 26 / 27 の共通。バケット daily-article:read)120 回 / 分通す

API-001-5 / 6 / 8 にはユーザー単位のレート制限を置かない(ブロックエディタからの呼び出しと共通のため)。

監査ログ ​

読み取り(API-001-19 / 20 / 23 / 26 / 27)は残さない。更新系(API-001-21 / 22 / 24 / 25)は BotRestAuditLogger で error_log に 1 行 JSON(接頭辞 [bot-rest-audit])を書く。リクエストボディ・考察本文・イベント名・〇〇の日の名前は書かない。

ルート記録する項目
項目更新ユーザー ID、post_id、結果(outcome)、送ったキーと変わったキーの件数、変わったキーごとの変更前後の文字数。保存を試みたときは save_path(hook_typed / hook_save_post / fallback)、typed_hook_fired、save_post_fired、フォールバック時は side_effects_rerun(done / skipped / wp_error)、失敗時は failure_reason と error_code
ゴミ箱移動ユーザー ID、post_id、結果、移動前のステータス
イベント登録ユーザー ID、ホール、結果(created / exists / failed)、イベント ID、イベント名の文字数
〇〇の日マスタ登録ユーザー ID、結果(created / updated / exists / mmdd_conflict / name_mismatch / failed)、マスタ ID(master_id)、名前の文字数、mmdd、is_show

権限・nonce ​

DailyArticleBotRestHandler + WordPressDailyArticleBotPermissionChecker。アプリケーションパスワード(Basic 認証)で呼ぶ前提のため nonce は検証しない。Cookie 認証で nonce が無いリクエストは WordPress コア(rest_cookie_check_errors)が未ログイン扱いにするため、ブラウザのセッションを流用した CSRF にはならない。Local バイパスの条件は WordPressPermissionChecker と同じ。

ルート必要な Capability
/articles・/events(GET / POST)edit_daily_articles
/birthdays・/birthdays/search(GET)edit_daily_articles(公開中の日別記事の誕生日ピックアップに出ている内容のため。紐付け担当の read_birthday_data は要らない)
/articles/{id}・/articles/{id}/fieldsedit_daily_articles と、その記事の edit_post
/articles/{id}/trash上に加えて trash_daily_article_drafts(ADM-035。既定は日別記事編集bot)または その記事の delete_post
/what-day-master(POST)edit_daily_articles と manage_what_day_master(ADM-035。既定は日別記事編集bot。〇〇の日マスタ管理画面の権限とは別)

記事 ID は権限判定・コントローラーとも URL パスの {id}(WP_REST_Request::get_url_params())だけを読む。ボディやクエリに id を付けても対象は変わらない(get_params() はボディ・クエリが URL より優先されるため使わない)。

記事が存在しない・日別記事でない ID は権限判定を通し、コントローラーが 404 を返す(ほかの投稿タイプの存在を 403 / 404 の違いで推測させないため、日別記事以外は一律 404)。