{
  "name": "Recipe Search API",
  "version": "20260922",
  "endpoints": {
    "/search": {
      "method": "GET",
      "description": "レシピを検索（キーワードマッチング）",
      "parameters": {
        "q": "キーワード（全文検索、スペース区切りでAND検索。各語はかな表記ゆれ〔ひらがな⇔カタカナ〕と類義語辞書で自動展開。辞書には双方向の表記ゆれ〔鱈⇔たら等〕と一方向の展開〔豚肉→豚、牛肉→牛、鶏肉・鳥肉・チキン→鶏もも肉・手羽先等の部位表記、ひき肉・ミンチ→合いびき〕があり、一方向は見出し語で検索したときだけ広がる〔手羽先で検索しても鶏肉全部には広がらない〕。名前一致先行の第1段〔sort参照〕も展開後の語で判定する）",
        "name": "レシピ名（/searchのqと同じ展開を適用）",
        "cuisine": "料理ジャンル（5区分: 和食/洋食/中華/韓国料理/エスニック。表記揺れ・サブジャンルを自動展開。他の値は完全一致。繰り返し指定/カンマ区切りで複数=OR）",
        "category": "正規化済みカテゴリー（主菜/副菜/主食/汁物/デザート/飲料/その他。recipeCategoryを保存時に正規化したcategory項目と完全一致で、確定した区分だけに絞る＝カテゴリー不明（category=null、非保有サイト分）は結果から落ちる。繰り返し指定/カンマ区切りで複数=OR）",
        "excludeCategory": "除外する正規化済みカテゴリー（確定した区分だけを除外し、不明（category=null）は残す。「主菜を探す」場面ではcategory=主菜でなくexcludeCategory=副菜,汁物の利用を推奨。繰り返し指定/カンマ区切りで複数=OR）",
        "maxCalories": "最大カロリー（99999kcal超の非現実値・0kcalは未入力とみなしカロリー不明扱い＝結果から落ちる）",
        "maxTime": "最大調理時間（分、「◯分以内」フィルタ。0分・10万分以上の非現実値は未入力とみなし調理時間不明扱い。不明のレシピは結果から落ちる〔残す場合はincludeUnknownTime=true〕）",
        "includeUnknownTime": "maxTime指定時に調理時間不明（未設定・0分等）のレシピも結果に残す（true/false、既定false。空は未指定扱い、他の値は400。時間欠損の多いサイト〔orangepage・lettuceclub〕がmaxTimeで丸ごと消えるのを避けるオプション。maxTime未指定時は作用しない）",
        "minServings": "人数の下限（範囲指定。単一指定はminServings=maxServings、「5人分以上」はminServingsのみ。レシピ側の「N〜M人分」レンジとは範囲の重なりで判定〔「2〜3人分」はminServings=3でもmaxServings=2でもヒット〕、recipeYieldの人数以外の数字〔個数/寸法/重量/容量〕・999人超の非現実値は人数として拾わない。minServings>maxServingsは400、整数以外は400）",
        "maxServings": "人数の上限（範囲指定。整数のみ）",
        "author": "著者名（部分一致フィルタ）",
        "ingredient": "材料（/searchのqと同じ展開を適用。豚肉で豚ロース肉等、鶏肉で鶏もも肉等の部位表記も一致）",
        "source": "レシピソース（ajinomoto, kikkoman, delishkitchen, erecipe, orangepage, kyounoryouri, lettuceclub, kewpie。繰り返し指定/カンマ区切りで複数=OR）",
        "sort": "並び替え: relevance（既定。qありは名前一致先行の2段方式〔第1段=レシピ名に全キーワード一致、第2段=説明・材料のみ一致、各段の中は公開日降順〕、qなしは公開日降順）, time_asc, time_desc, calories_asc, calories_desc, published_desc（time_asc/descは調理時間0分・未設定を、calories_asc/descは0kcal・未設定を末尾に配置）",
        "limit": "表示件数（デフォルト: 10）"
      },
      "note": "各itemは正規化済みカテゴリーのcategory項目（主菜/副菜/主食/汁物/デザート/飲料/その他、不明はnull）を含む（#927。生のrecipeCategoryとは別項目）",
      "example": "/search?q=トマト チーズ&source=kikkoman,ajinomoto&cuisine=洋食&maxCalories=300&limit=5"
    },
    "/search/vector": {
      "method": "GET",
      "description": "レシピを意味的類似性で検索（PLaMo-Embedding-1B使用）",
      "parameters": {
        "q": "検索クエリ（必須、スペース区切りも可。埋め込みベクトルによる意味検索のため類義語辞書・部位表記の展開は適用しない〔ingredientフィルタには適用する〕）",
        "cuisine": "料理ジャンル（5区分対応、/searchと同じ。繰り返し指定/カンマ区切りで複数=OR）",
        "category": "正規化済みカテゴリー（主菜/副菜/主食/汁物/デザート/飲料/その他、/searchと同じ。不明（null）は結果から落ちる。繰り返し指定/カンマ区切りで複数=OR）",
        "excludeCategory": "除外する正規化済みカテゴリー（確定区分だけを除外し不明は残す、/searchと同じ。繰り返し指定/カンマ区切りで複数=OR）",
        "maxCalories": "最大カロリー（99999kcal超の非現実値・0kcalは未入力とみなしカロリー不明扱い＝結果から落ちる）",
        "maxTime": "最大調理時間（分。0分・10万分以上は調理時間不明扱い、不明は結果から落ちる。詳細は/searchと同じ）",
        "includeUnknownTime": "maxTime指定時に調理時間不明のレシピも結果に残す（true/false、既定false。詳細は/searchと同じ）",
        "minServings": "人数の下限（範囲指定。レシピ側の「N〜M人分」レンジとは範囲の重なりで判定、人数以外の数字・999人超の非現実値は拾わない。minServings>maxServingsは400、整数以外は400。詳細は/searchと同じ）",
        "maxServings": "人数の上限（範囲指定。整数のみ）",
        "author": "著者名（部分一致）",
        "ingredient": "材料（/searchのqと同じ展開を適用。豚肉で豚ロース肉等、鶏肉で鶏もも肉等の部位表記も一致）",
        "source": "レシピソース（8サイト、/searchと同じ。繰り返し指定/カンマ区切りで複数=OR）",
        "limit": "表示件数（デフォルト: 10）"
      },
      "note": "「さんま」「サンマ」「秋刀魚」などの表記揺れに対応。並び順は類似度（コサイン距離）固定のためsortは非対応。各itemは正規化済みカテゴリーのcategory項目（不明はnull）を含む",
      "example": "/search/vector?q=秋刀魚 大根&maxTime=30&limit=5"
    },
    "/search/hybrid": {
      "method": "GET",
      "description": "ハイブリッド検索（RRF: キーワード検索とベクトル検索を統合）",
      "parameters": {
        "q": "検索クエリ（必須、スペース区切りでAND検索。keyword側は/searchのqと同じ展開〔類義語辞書・部位表記の一方向展開〕を適用、vector側は展開せず意味で照合）",
        "limit": "表示件数（デフォルト: 10）",
        "k": "RRFパラメータ（デフォルト: 60）",
        "candidates": "B-2方式の候補絞り込み件数N（デフォルト: 100、relevance以外のソート時のみ有効）",
        "weight": "RRF合成の字句（キーワード）側の重み（0〜1、デフォルト: 0.6。意味側は1-weight。weight=0.5で従来の対等合成を再現。範囲外・数値以外は400）",
        "cuisine": "料理ジャンル（5区分: 和食/洋食/中華/韓国料理/エスニック。表記揺れ・サブジャンルを自動展開。生のcuisine値も後方互換で完全一致。繰り返し指定/カンマ区切りで複数=OR）",
        "category": "正規化済みカテゴリー（主菜/副菜/主食/汁物/デザート/飲料/その他、/searchと同じ。不明（null）は結果から落ちる。繰り返し指定/カンマ区切りで複数=OR）",
        "excludeCategory": "除外する正規化済みカテゴリー（確定区分だけを除外し不明は残す、/searchと同じ。繰り返し指定/カンマ区切りで複数=OR）",
        "ingredient": "材料（フィルタ。/searchのqと同じ展開を適用し、豚肉で豚ロース肉等、鶏肉で鶏もも肉等の部位表記も一致）",
        "source": "レシピソース（ajinomoto, kikkoman, delishkitchen, erecipe, orangepage, kyounoryouri, lettuceclub, kewpie。繰り返し指定/カンマ区切りで複数=OR）",
        "author": "著者名（部分一致フィルタ）",
        "maxCalories": "最大カロリー（フィルタ。99999kcal超の非現実値・0kcalは未入力とみなしカロリー不明扱い＝結果から落ちる）",
        "maxTime": "最大調理時間（分、「◯分以内」フィルタ。0分・10万分以上は調理時間不明扱い、不明は結果から落ちる〔残す場合はincludeUnknownTime=true〕）",
        "includeUnknownTime": "maxTime指定時に調理時間不明のレシピも結果に残す（true/false、既定false。詳細は/searchと同じ）",
        "minServings": "人数の下限（範囲指定。単一指定はminServings=maxServings、「5人分以上」はminServingsのみ。レシピ側の「N〜M人分」レンジとは範囲の重なりで判定〔「2〜3人分」はminServings=3でもmaxServings=2でもヒット〕、recipeYieldの人数以外の数字〔個数/寸法/重量/容量〕・999人超の非現実値は人数として拾わない。minServings>maxServingsは400、整数以外は400）",
        "maxServings": "人数の上限（範囲指定。整数のみ）",
        "sort": "並び替え: relevance（既定/重み付きRRFスコア順）, time_asc, time_desc, calories_asc, calories_desc, published_desc（time_asc/descは調理時間0分・未設定を、calories_asc/descは0kcal・未設定を末尾に配置）"
      },
      "note": "完全一致と意味的類似性の両方を考慮した最適な検索。フィルタはベース2検索（keyword/vector）両方に適用される。sort指定（relevance以外）時はB-2方式：RRF上位N件（candidates、既定100）に絞ってからDB側で対象フィールドを並び替えるため関連性が保たれる。このときitemsのrrfScore/keywordRank/vectorRankは含まれない。各itemは正規化済みカテゴリーのcategory項目（不明はnull）を含む",
      "example": "/search/hybrid?q=トマト&cuisine=和風&maxTime=30&sort=calories_asc&limit=5"
    },
    "/stats": {
      "method": "GET",
      "description": "レシピ総数と料理ジャンル別集計を取得",
      "returns": {
        "recipes": "レシピ総数",
        "cuisines": "料理ジャンル別集計"
      },
      "example": "/stats"
    },
    "/health": {
      "method": "GET",
      "description": "ヘルスチェック"
    }
  }
}