# Webカタログ検索API(β) 最近、AIエージェントによるWebカタログへの検索アクセス数が増加しており、その負荷が問題となっています。 HTMLレンダリングの負荷軽減のため、プログラムからの検索アクセス用のAPIの提供を試験的に開始します。 ## 注意事項 - 本APIの提供は予告なく停止する可能性があります。 - レートリミット・IPアドレス制限等を実施することがあります。基準は明示しません。 - 本APIは、Webカタログ公開開始日時から、イベント開催終了日時の7日後まで提供します。 - 検索・閲覧を目的とした利用のみとし、その他の用途での利用は禁止します。 - 利用方法に関するご質問・ご要望にはお答えいたしません。 ### お願い - 同じ検索条件の結果は一定時間キャッシュし、同一の要求を繰り返さないでください。ページ・並び順も含めてキャッシュを区別してください。 - ログインセッションのCookieをユーザー間で共有しないでください。 - 全件取得は禁止です。条件を分割したりページを巡回したりして全件を収集する行為も含みます。 - 気になるテーマがいくつかあるときは、ひとつずつ検索する代わりに、OR条件でなるべくまとめて探してください。例えば「他者」「表象」「言説」をめぐる本を探したいときは、`?s=他者,表象,言説` と書けば、いずれかの言葉を含む出店・作品を一度に検索できます。異なる項目の条件も `*` や括弧によるORでまとめられます。 ## 基本仕様 本API・この仕様書・`/llms.txt`・MCPは文学フリマ用の `bunfree_*` 環境でのみ提供します。 ### アクセス方法の選び方 - **Web閲覧・URL取得ツールからは、GETのJSON APIを使ってください。** `/c/api/v1/events` で開催回を確認し、`/c/api/v1/:event_abbrev/search/results` に条件を付けてGETします。MCPの接続設定は不要です。 - **このカタログのMCPに接続済みなら、`list_events`・`search_catalog` ツールを直接呼び出してください。** 引数はJSONで渡し、URLエンコードしません。 `/c/api/v1/mcp` はMCPクライアントの接続設定用URLです。通常のWeb閲覧ツールでGETしても検索できません。MCPのツールが利用可能でなければ、GETのJSON APIを使ってください。 ### 読み取り専用MCP(β) 既存のGET JSON APIに加え、`/c/api/v1/mcp` に認証不要のMCPを提供します。接続先には、このサイトのオリジンを含む完全なURLを指定してください。Streamable HTTP、プロトコル `2025-06-18` に対応し、POSTへの応答はJSONです。セッションID・SSEストリームは使用せず、GET・DELETEには405を返します。クライアント側でMCPの接続設定が必要です。 - `list_events`:引数なし。利用可能な開催回を取得します。 - `search_catalog`:`event` に開催回の `abbrev` を指定し、少なくとも1つの検索条件を付けます。`keywords` は語の配列(OR)、`all_keywords` は語の配列(AND)です。その他の条件同士はANDです。文字列はURLエンコードせず、そのまま渡してください。 - 項目指定:`name`・`title`・`author`・`category`・`instagram`・`twitter`・`booth`。 - 範囲指定:`price_min`・`price_max`・`pages_min`・`pages_max`(整数)、`issued_after`・`issued_before`(YYYY-MM-DD、境界を含む)。`has_cover` は書影登録の有無です。 - `sort` は並び順の配列、`page`・`per_page` は整数です。指定可能な値と取得上限はJSON APIと共通です。引数の正確なスキーマは `tools/list` で取得できます。 検索引数の例:`{"event":"tokyo43","keywords":["他者","表象","言説"],"per_page":10}`。開催回の略称は `list_events` の結果に合わせてください。 結果は `structuredContent` とJSON文字列の `content` に返し、検索結果の構造はJSON APIと共通です。MCPの `web_url` は絶対URLです。検索条件の誤りや利用不可の開催回は `isError: true` で返します。提供期間・公開範囲・取得件数上限・全件取得禁止・キャッシュのお願いはJSON APIと共通です。レート制限は同一IPのJSON APIとMCPを合算します。MCPではログインCookieを送っても個人情報や「気になる!」の状態を検索・変更できません。 ### リソース | パス | メソッド | 用途 | | --- | --- | --- | | `/c/api/v1/events` | GET | イベント一覧 | | `/c/api/v1/:event_abbrev/search/floors` | GET | フロア一覧 | | `/c/api/v1/:event_abbrev/search/categories` | GET | カテゴリ一覧 | | `/c/api/v1/:event_abbrev/search/results` | GET | 出店・作品の検索 | - `:event_abbrev` は開催回の略称です。 ### エラーコード | 状態 | HTTPステータス | | --- | --- | | 不正な条件、条件なし、値の型・範囲の誤り | 400 | | 未ログインで `watch` 条件を指定 | 401 | | 開催回が存在しない、API提供期間外 | 404 | | クエリ文字列のサイズ超過 | 414 | | レート制限 | 429 | ## イベント一覧API - 読み取りのみで、書き込みはできません。 - Webカタログで公開されている情報のみ参照できます。個人情報その他の非公開情報は取得できません。 ### クエリ例 ```http GET /c/api/v1/events ``` クエリパラメータは付けません。現在、検索APIを利用できる公開中の開催回を、開催開始日時・IDの昇順で返します。 ### レスポンス例 ```json { "events": [ { "id": 101, "name": "文学フリマ東京43", "abbrev": "tokyo43", "start": "2026-11-01T12:00:00+09:00", "finish": "2026-11-01T17:00:00+09:00", "website_url": "https://example.com/events/tokyo43", "location_sub": "第一展示場・第二展示場", "location": { "id": 10, "name": "文学交流会館", "prefecture": "東京都", "city": "千代田区", "address": "丸の内1-1-1", "website_url": "https://example.com/venue" } } ] } ``` ※例は架空の内容です。 - `id`:数値の開催回ID。「気になる!」の操作では `event_id` に指定できます。 - `name`:開催回の名称。 - `abbrev`:開催回の略称。他のAPIの `:event_abbrev` に指定します。 - `start`・`finish`:ISO 8601形式の開催開始・終了日時。 - `website_url`:イベントの公式WebサイトURL。未登録の場合は `null`。 - `location_sub`:会場内の部屋・ホール名。未登録の場合は `null`。 - `location`:開催会場の情報。未設定の場合は `null`。 - `id`・`name`:会場ID・会場名。 - `prefecture`・`city`・`address`:都道府県名・市区町村・それ以降の住所。これらを順につなげると会場の住所になります。 - `website_url`:会場のWebサイトURL。未登録の場合は `null`。 対象の開催回がない場合は `{"events": []}` を返します。 ## フロア一覧 指定した開催回のフロア一覧を返します。 各フロアには `id`(フロアID)、`name`(名称)、`abbrev`(略称)を含みます。 検索時は、取得した `id` を `floor_id` に指定します。 表示順・ID順で全件を返し、ページングや検索条件の指定は受け付けません。 ### クエリ例 開催回の略称が `tokyo43` の場合: ```http GET /c/api/v1/tokyo43/search/floors ``` クエリパラメータは付けません。 ### レスポンス例 ```json { "floors": [ { "id": 3, "name": "第一展示場", "abbrev": "Hall1" }, { "id": 4, "name": "第二展示場", "abbrev": "Hall2" } ] } ``` ※例は架空の内容です。 ## カテゴリ一覧 指定した開催回のカテゴリ一覧を `categories` 配列で返します。 各カテゴリには `id`(カテゴリID)、`name`(名称)を含みます。 ここで取得した `name` を API での `category` に指定します。 カテゴリ名は部分一致で検索します。 表示順・ID順で全件を返し、ページングや検索条件の指定は受け付けません。 ### クエリ例 開催回の略称が `tokyo43` の場合: ```http GET /c/api/v1/tokyo43/search/categories ``` クエリパラメータは付けません。 ### レスポンス例 ```json { "categories": [ { "id": 11, "name": "小説|純文学" }, { "id": 12, "name": "小説|SF" }, { "id": 13, "name": "評論|文学" } ] } ``` ※例は架空の内容です。 ## 出店・作品の検索 - 指定した開催回の公開中の出店・作品を検索し、一致した候補を `results` 配列で返します。 - 検索条件は1つ以上指定してください。条件なしの要求は400を返します。 ### レスポンス例 - 各候補には出店情報の `entry`、作品情報の `item`、Webカタログの詳細ページへのパス `web_url` を含みます。 - `entry.category` はカテゴリの `id`・`name` を含むオブジェクトです。カテゴリ未設定の場合は `null` を返します。 - `entry.booths` はブース情報のオブジェクト配列です。各要素に `name`(ブース番号、例:`A-20`)と `floor_name`(フロア名、例:`第一展示場`)を含み、複数ブースの場合は番号ごとに返します。配列内は常に接頭辞(prefix)・番号(number)の昇順で、番号は数値として並べます。検索結果の `sort` の指定には影響されません。単独ブースでも配列となり、未割当・ブース番号の公開前は空配列 `[]` を返します。 - `item.images` は書影画像の絶対URLをサイズ別に返すオブジェクトです。`small` は最大200×200px、`medium` は最大600×600px、`large` は最大1200×1200pxです。縦横比を維持し、元画像が小さい場合は拡大しません。書影未登録の場合はオブジェクト全体が `null` になります。 - `item.created_at`・`item.updated_at` は作品情報の登録日時・変更日時です。タイムゾーンと秒の小数部を含むISO 8601形式で返します。発行日や、この開催回への掲載日時とは異なります。 - 作品未登録の出店が条件に一致した場合、`item` は `null` になります。 - 同じ作品でも掲載元の出店が異なれば別の候補として扱います。一致する候補がない場合は `{"count": 0, "next": false, "results": []}` を返します。 - `count`:検索条件に一致する候補の件数(最大100)。ページに関係なく同じ値を返し、100は「100件以上」を意味します。 - `next`:次ページがある場合は `true`、それ以外は `false`。 - `results`:指定したページの候補。`count` は出店数ではなく、出店と作品の組み合わせの数です。 ```json { "count": 1, "next": false, "results": [ { "entry": { "id": 201, "name": "星空文庫", "description": "幻想小説を中心に制作しています。", "category": { "id": 11, "name": "小説" }, "booths": [ { "name": "A-20", "floor_name": "第一展示場" }, { "name": "A-21", "floor_name": "第一展示場" } ] }, "item": { "id": 301, "title": "夜の図書館", "author": "宮澤太郎", "description": "孤独と喪失を描く短編集。", "images": { "small": "https://example.com/images/small/cover-301.jpg", "medium": "https://example.com/images/medium/cover-301.jpg", "large": "https://example.com/images/large/cover-301.jpg" }, "price": "1000.00", "issued_at": "2026-09-01", "created_at": "2026-09-01T12:00:00.000000+09:00", "updated_at": "2026-10-01T18:30:00.000000+09:00" }, "web_url": "/p/tokyo42/301" } ] } ``` ※例は架空の内容です。 ### 検索条件 GET パラメータを使って条件を指定します。 #### 検索項目 | クエリパラメータ名 | 内容 | | --- | --- | | `s` | 出店名・読み・紹介・メンバー・SNS、作品名・読み・著者・著者の読み・作品紹介の横断検索 | | `entry_id`, `item_id` | 出店掲載ID、作品ID | | `name`, `kana` | 出店名、読み | | `description`, `members` | 出店紹介、メンバー | | `twitter`, `instagram`, `website` | 出店のSNSアカウント、WebサイトURL | | `category` | カテゴリ名の部分一致 | | `floor_id` | フロアID | | `booth` | ブース番号 | | `title`, `title_kana` | 作品名、読み | | `author`, `author_kana` | 著者、読み | | `text` | 作品紹介 | | `url`, `isbn`, `isdn` | 作品URL、識別番号 | | `youtube`, `nicovideo` | 動画ID | | `type_id` | 作品種別ID | | `price`, `pages` | 円単位の価格、ページ数 | | `issued_at` | 発行日(YYYY-MM-DD。時刻は含みません) | | `created_at`, `updated_at` | 作品情報の登録日時・変更日時(タイムゾーン付きISO 8601形式) | | `image_url` | 書影の登録有無(`=*` / `!=*` のみ) | | `watch` | 自分の「気になる!」登録状態(`true` / `false`、ログイン必須) | 発行日の検索条件・ソートは、互換性のため旧名 `issued` も受け付けます。レスポンスでは `item.issued_at` を返します。 部分一致のワイルドカードには対応していません。値が `*` 単独の場合は、以下の存在チェックになります。 #### 閉区間・開区間の指定 `price`・`pages`・`issued_at`・`created_at`・`updated_at` は、値の先頭に比較演算子を付けて範囲を指定できます。 | 値の書き方 | 比較 | 意味(価格の場合) | | --- | --- | --- | | `1000` | `= 1000` | 1,000円と等しい | | `+1000` | `>= 1000` | 1,000円以上(境界を含む) | | `-1000` | `<= 1000` | 1,000円以下(境界を含む) | | `+*1000` | `> 1000` | 1,000円を超える(境界を含まない) | | `-*1000` | `< 1000` | 1,000円未満(境界を含まない) | 下限と上限は、同じ項目を繰り返して `&` でつなぎます。 | 区間 | 価格の条件 | クエリ例 | | --- | --- | --- | | 閉区間 `[1000, 10000]` | 1,000円以上、10,000円以下 | `?price=+1000&price=-10000` | | 開区間 `(1000, 10000)` | 1,000円を超え、10,000円未満 | `?price=+*1000&price=-*10000` | | 半開区間 `[1000, 10000)` | 1,000円以上、10,000円未満 | `?price=+1000&price=-*10000` | | 半開区間 `(1000, 10000]` | 1,000円を超え、10,000円以下 | `?price=+*1000&price=-10000` | 下限だけ、上限だけの指定も可能です。区間表記の `[]` や `()` を値に書くのではなく、上記の比較演算子を使います。 発行日も同じ規則です。`+` は指定日以降、`-` は指定日以前、`+*` は指定日より後、`-*` は指定日より前を表します。 例: 2026年1月1日を含み、2027年1月1日を含まない場合 ```text ?issued_at=+2026-01-01&issued_at=-*2027-01-01 ``` #### 登録日時・変更日時の範囲検索 登録日時・変更日時も同じ比較演算子を使います。日時は `2026-10-01T00:00:00+09:00` または `2026-09-30T15:00:00Z` のように秒・タイムゾーンまで指定してください。秒の小数部は最大6桁まで指定できます。日付だけ、またはタイムゾーンのない日時は400になります。 例えば、日本時間で2026年10月中に作品情報が変更されたものを、新しく変更された順に探す場合: ```text ?updated_at=+2026-10-01T00:00:00%2B09:00&updated_at=-*2026-11-01T00:00:00%2B09:00&sort=-updated_at ``` `%2B` はタイムゾーンの `+` を表します。登録日時で探す場合は `updated_at` を `created_at` に変えます。`created_at=*`・`updated_at=*` の存在チェックにも対応します。 #### 条件間のAND・OR・NOT | 記法 | 意味 | | --- | --- | | `price=+1000` | AND:1,000円以上 | | `price*=+1000` | OR:1,000円以上 | | `price!=+1000` | AND NOT:1,000円以上ではない | | `price!*=+1000` | OR NOT:1,000円以上ではない | 条件間は左から順番に評価します。値の中のキーワード評価とは区別します。 NOTはその条件全体を否定します。先頭条件にORは指定できません。 ```text ?author=宮澤&price*=+1000&price=-10000 ``` 意味は「(著者が宮澤に一致 OR 価格が1,000円以上)AND 価格が10,000円以下」です。 #### ページング 検索結果は `per_page` と `page` で取得件数・ページを指定できます。 | パラメータ | 内容 | 省略時 | 指定可能な値 | | --- | --- | --- | --- | | `per_page` | 1ページあたりの件数 | `20` | `1`〜`100` の整数 | | `page` | ページ番号 | `1` | `1`〜`5`、または `-1`〜`-5` の整数 | 例: 小説カテゴリの作品を価格昇順で並べ、1ページ20件として2ページ目を取得する場合 ```text ?category=小説&sort=price&per_page=20&page=2 ``` この例では21〜40件目を返します。結果がその位置までない場合は `results` が空配列、`next` が `false` になります。`count` はその場合も検索条件に一致する候補の件数(最大100)です。 ページを移動するときは、検索条件・`sort`・`per_page` を維持して `page` だけを変更してください。 1つの検索条件で取得できる範囲は先頭100件までです。 例えば `per_page=60&page=2` は61〜100件目の最大40件を返し、`per_page=60&page=3` は範囲外のため400を返します。 `page=-1` は取得可能な範囲の最後のページ、`page=-2` はその1つ前を指定します。 ページ内の並び順は変わりません。対象は最大5ページ・先頭100件までで、全検索結果の末尾ではありません。 例えば101件以上一致しても、`per_page=60&page=-1` は61〜100件目を返します。 `per_page=10` の場合は最大5ページなので、51件以上一致しても `page=-1` は41〜50件目を返します。 ```text ?category=小説&sort=price&per_page=20&page=-1 ``` 存在するページより前を指す負の指定は400になります。検索結果が0件の場合、`page=-1` は空配列を返します。 `page=0`・`page=-6`・小数・範囲外の値や、同じページングパラメータの重複指定も400になります。 `per_page` に負数は指定できません。 `per_page`・`page` だけでは検索できません。検索条件も1つ以上指定してください。 これらは条件式の括弧の外に指定します。フロア一覧・カテゴリ一覧には適用しません。 #### ソート `sort` はカンマ区切りで複数指定でき、左から優先します。先頭に `-` をつけると降順になります。 `sort=booth` はブースの接頭辞・開始番号の順で並べます。番号は数値順(2、10の順)で、複数ブースにまたがる出店は開始番号を使います。`sort=-booth` は降順です。ブース未割当の出店は昇順・降順とも末尾になります。ブース番号の公開前は400を返します。 他の並び順とも組み合わせられます(例:`sort=booth,price`)。同順位の場合は出店ID・作品IDの昇順となり、順序は安定しています。 ```text ?category=小説&sort=booth,price ``` 例: 小説カテゴリの作品を価格昇順(安い順)でソート ```text ?category=小説&sort=price ``` 例: 小説カテゴリの作品を発行日順(新しい順)でソート ```text ?category=小説&sort=-issued_at ``` #### 条件式 | 記法 | 意味 | | --- | --- | | `&(条件式)` | AND | | `&*(条件式)` | OR | | `&!(条件式)` | AND NOT | | `&*!(条件式)` | OR NOT | ##### 例 著者名に「宮澤」を含む作品、または価格が1,000円以上かつ10,000円以下の作品を検索する例です。 ```text ?author=宮澤&*(price=+1000&price=-10000) ``` 著者条件に部分一致するか、1,000〜10,000円の価格条件に一致します。 括弧の入れ子に対応します。各グループ内も左から評価します。 #### 複数キーワードでの検索 - `,`: OR - `+`: AND 例: 「田中」または「大平」または「中曽根」で検索する場合 ```text ?s=田中,大平,中曽根 ``` 例: 「松陰」かつ「吉田」で検索する場合 ```text ?s=吉田+松陰 ``` #### 値の有無で検索 `項目=*` は値が登録されている候補、`項目!=*` は登録されていない候補に一致します。 例えば、Xアカウントが登録されている出店は `?twitter=*`、Instagramが登録されていてXは未登録の出店は `?instagram=*&twitter!=*` で検索できます。 文字列項目では、`null`・空文字・空白だけの値は「未登録」として扱います。数値・日付・IDは `null` の場合だけ未登録となり、数値の `0` は値ありです。`price=*` なら価格が登録されている作品を検索できます。 `s=*` は横断検索対象のどれかに値がある候補、`booth=*` はブース割当のある出店に一致します。ブース条件は公開後のみ利用できます。`watch` は対象外で、従来どおり `true` / `false` を指定してください。 `*` 単独の場合だけ特別な意味を持ち、`twitter=foo*` は文字列 `foo*` の検索です。文字としての `*` 単独を検索するときは `%2A` と記述してください。 AND・OR・NOTや括弧とも組み合わせられます。 値の有無を使った検索例: | クエリ | 検索対象 | | --- | --- | | `?website=*` | Webサイトが登録されている出店 | | `?text=*` | 作品紹介が登録されている作品 | | `?isbn=*` | ISBNが登録されている作品 | | `?issued_at=*` | 発行日が登録されている作品 | | `?pages=*` | ページ数が登録されている作品 | | `?price=*` | 価格が登録されている作品(0円を含む) | | `?booth=*` | ブースが割り当てられている出店(ブース公開後のみ) | | `?item_id!=*` | 作品が登録されていない出店 | | `?s=他者,表象,言説&website=*` | いずれかの言葉を含む出店・作品のうち、出店のWebサイトが登録されているもの | | `?twitter=*&instagram*=*` | XまたはInstagramの少なくとも一方が登録されている出店 | #### 「気になる!」の登録状態で検索 `watch=true` は自分が「気になる!」に登録した出店、`watch=false` は未登録または解除済みの出店に一致します。 登録状態は作品単位ではなく出店単位で、その出店の各作品に適用します。「訪問済」や「あとで」の状態は含めません。 ```text ?watch=true&price=-1000&sort=price ``` この例は「気になる!」に登録した出店の、価格が1,000円以下の作品を返します。 値は小文字の `true` または `false` を指定します。AND・OR・NOTや括弧と組み合わせられます。 `watch` を使用するときは本人のログイン済みセッションCookieが必要です。 未ログインの場合は、`watch=false` やOR・NOTの中での指定も含め401を返します。 他の利用者の登録状態を参照したり、検索条件で利用者IDを指定したりすることはできません。 GET検索は登録状態を変更せず、CSRFトークンも不要です。`watch` を指定しない検索は未認証で利用できます。 #### クエリ例 以下の `?` から始まる例は、`/c/api/v1/:event_abbrev/search/results` の後ろに付けて使います。 カテゴリ名はカテゴリ一覧API、フロアIDはフロア一覧APIで確認してください。例のID・ブース番号・人名は架空のものです。 キーワードは登録された文字列に対する検索です。テーマや内容を自動で判定するものではありません。 ##### 気になっている著者の本をまとめて探す 著者名に「宮澤太郎」または「夏目花子」を含む作品を、著者名の昇順、同じ著者名では発行日の新しい順で並べます。 ```text ?author=宮澤太郎,夏目花子&sort=author,-issued_at ``` ##### 今年発行された評論を探す カテゴリ名に「評論」を含む出店のうち、2026年に発行された作品を新しい順に探します。カテゴリは作品自体ではなく、掲載元の出店のカテゴリです。 ```text ?category=評論&issued_at=+2026-01-01&issued_at=-2026-12-31&sort=-issued_at,title ``` ##### 金額の大きい本を探す 価格が登録されている作品を、金額の大きい順に表示します。 ```text ?price=*&sort=-price,title ``` ##### ページ数の多い本を探す ページ数が登録されている作品を、ページ数の多い順に表示します。 ```text ?pages=*&sort=-pages,title ``` ##### 書影が登録されている本を探す 書影が登録されている作品を検索します。検索結果の `item.images.small`・`item.images.medium`・`item.images.large` から用途に合った書影を参照できます。検索条件名は引き続き `image_url` を使います。 ```text ?image_url=* ``` テーマで絞り込むこともできます。例えば「他者」「表象」「言説」のいずれかを含み、書影が登録されている作品を探す場合: ```text ?s=他者,表象,言説&image_url=* ``` ##### 当日訪ねたい出店をブース順で確認する 「気になる!」に登録した出店から、指定フロアでブースが割り当てられているものをブース順に表示します。 ログインセッションのある状態でのアクセスが必要です。 同じ出店に複数の作品がある場合は、作品ごとに候補が返ります。 ```text ?watch=true&floor_id=3&booth=*&sort=booth,title ``` ##### ブース番号検索 ```text ?booth=A20 ``` フロアIDが `3` で、ブース番号が `A-20` または `A-21` の出店を検索します。 ```text ?floor_id=3&(booth=A20&booth*=A21) ``` ##### 発行日検索 発行日が2026年1月1日以降、2026年12月31日以前のものを検索するとき: ```text ?issued_at=+2026-01-01&issued_at=-2026-12-31 ``` ## 「気になる!」の登録・解除 Webカタログの「気になる!」への登録・解除に利用できます。 - 「気になる!」の登録と解除ができます。 - 登録は、ユーザー・開催回・出店の組み合わせごとに1件です。 - 他のユーザーのデータは参照できません。 - 検索結果の `entry.id` が、このAPIでいう `exhibit_id` に対応します。 ### HTTPで操作する場合 「気になる!」の登録・解除・参照には利用者本人のログイン済みセッションが必要です。 - セッションCookieを維持します。 - 同じセッションで取得したカタログHTMLの `` からトークンを取得し、`X-CSRF-Token` ヘッダーに付けます。 - `Accept: application/json` と `Content-Type: application/json` を指定します。 - `event_id` は対象ボタンの属性から取得します。`tokyo43` のような略称や、申込IDではありません。 フラグIDがまだない場合は、以下のPOSTを使います。IDと値は架空の例です。 ```http POST /api/exhibit_flag Accept: application/json Content-Type: application/json X-CSRF-Token: <同じセッションのトークン> { "exhibit_flag": { "event_id": 101, "exhibit_id": 201, "watch": true } } ``` 成功時は200と次の形式のJSONを返します。 ```json { "id": 501, "event_id": 101, "exhibit_id": 201, "watch": true, "done": false, "later": false } ``` フラグIDが分かる場合、更新は `PUT /api/exhibit_flag/501` です。 本文はPOSTと同じ形式で、解除する場合は `watch: false` を指定します。 Webカタログの画面でも、`flag_id` が空ならPOST、存在すればPUTを使用します。 APIは「反転」ではなく、`watch` を指定した真偽値に設定します。 同じ利用者・開催回・出店へ同じPOSTを再送してもフラグは重複作成されず、同じ状態になります。 変更しない `done`(訪問済)や `later` は送信しないでください。省略した状態は維持されます。 解除してもフラグの行は残るため、返された `id` を引き続き使用できます。 | 応答 | 意味・対応 | | --- | --- | | 200 | JSONの `event_id`・`exhibit_id`・`watch` を確認して完了を判断 | | 400 | 本文の形式や必須パラメータを確認 | | 401 | ログインが必要。利用者にログインを案内 | | 404 | 対象を確認できない。開催回・出店・公開状態を確認 | | 409 | CSRF検証失敗など。ページを再取得してセッションとトークンを確認 | 状態取得用のGETや削除用のDELETEは提供していません。画面のボタンか、更新応答のJSONで状態を確認してください。